Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions x/clp/keeper/guard_rails.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package keeper

import (
"fmt"

"github.com/Sifchain/sifnode/x/clp/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// Exit if the received asset amount is not above the minimum
func (k Keeper) CheckSentAmount(sentAmount sdk.Uint, minReceivingAmount sdk.Uint, to types.Asset) error {
if sentAmount.LT(minReceivingAmount) {
return types.ErrSwapAmountTooSmall
}
return nil
}

// Exit if the pool balance is not above the threshold
func (k Keeper) CheckPoolHealth(ctx sdk.Context, pool types.Pool) error {
if !k.IsPoolHealthy(ctx, pool) {
return types.ErrPoolNotHealthy
}
return nil
}

// Exit if the swap fee is not the expected one
func (k Keeper) CheckSwapFee(ctx sdk.Context, pool types.Pool, to types.Asset, marginEnabled bool, expectedSwapFee sdk.Dec) error {
from, _ := pool.GetPoolAsset(to)
swapFeeRate := k.GetSwapFeeRate(ctx, from, marginEnabled)
if swapFeeRate.Abs().Sub(expectedSwapFee).GTE(sdk.MustNewDecFromStr("0.000000000000000001")) {
return fmt.Errorf("swap fee is not the expected one, got %s, expected %s", swapFeeRate.String(), expectedSwapFee.String())
}
return nil
}

// Price impact affects the final price of the asset, good to have a limit
func (k Keeper) CheckPriceImpact(ctx sdk.Context, pool types.Pool, to types.Asset, sentAmount sdk.Uint) error {
_, Y, toRowan := pool.ExtractValues(to)
X, _ := pool.ExtractDebt(Y, Y, toRowan)
priceImpact := CalcPriceImpact(X, sentAmount)
// TODO: should be a parameter
if priceImpact.GTE(sdk.MustNewDecFromStr("0.1")) {
return fmt.Errorf("price impact is too high, got %s", priceImpact.String())
}
return nil
}
12 changes: 11 additions & 1 deletion x/clp/keeper/invariants.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
)

func RegisterInvariants(registry sdk.InvariantRegistry, k Keeper) {
// registry.RegisterRoute(types.ModuleName, "balance-module-account-check", k.BalanceModuleAccountCheck())
registry.RegisterRoute(types.ModuleName, "balance-module-account-check", k.BalanceModuleAccountCheck())
registry.RegisterRoute(types.ModuleName, "pool-units-check", k.UnitsCheck())
registry.RegisterRoute(types.ModuleName, "swap-in-out-check", k.SwapPriceInvariant())
}

func (k Keeper) BalanceModuleAccountCheck() sdk.Invariant {
Expand Down Expand Up @@ -116,3 +118,11 @@ func (k Keeper) UnitsCheck() sdk.Invariant {
return "all pool units vs total lp units match", false
}
}

func (k Keeper) SwapPriceInvariant() sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {

// Let's not check this for now as this is checked on every swap now.
return "swap price check is temporarily disabled", false
}
}
91 changes: 91 additions & 0 deletions x/clp/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,97 @@ func (k msgServer) CreatePool(goCtx context.Context, msg *types.MsgCreatePool) (

func (k msgServer) Swap(goCtx context.Context, msg *types.MsgSwap) (*types.MsgSwapResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
// Basic validation
if msg.SentAmount.IsZero() || msg.SentAmount.IsNegative() {
return nil, types.ErrSwapAmountTooSmall
}
// TODO: should we check also the min amount?
if msg.MinReceivingAmount.IsZero() || msg.MinReceivingAmount.IsNegative() {
return nil, types.ErrSwapAmountTooSmall
}
// Check assets
registry := k.tokenRegistryKeeper.GetRegistry(ctx)
sAsset, err := k.tokenRegistryKeeper.GetEntry(registry, msg.SentAsset.Symbol)
if err != nil {
return nil, types.ErrTokenNotSupported
}
rAsset, err := k.tokenRegistryKeeper.GetEntry(registry, msg.ReceivedAsset.Symbol)
if err != nil {
return nil, types.ErrTokenNotSupported
}
if !k.tokenRegistryKeeper.CheckEntryPermissions(sAsset, []tokenregistrytypes.Permission{tokenregistrytypes.Permission_CLP}) {
return nil, tokenregistrytypes.ErrPermissionDenied
}
if !k.tokenRegistryKeeper.CheckEntryPermissions(rAsset, []tokenregistrytypes.Permission{tokenregistrytypes.Permission_CLP}) {
return nil, tokenregistrytypes.ErrPermissionDenied
}
if k.tokenRegistryKeeper.CheckEntryPermissions(sAsset, []tokenregistrytypes.Permission{tokenregistrytypes.Permission_DISABLE_SELL}) {
return nil, tokenregistrytypes.ErrNotAllowedToSellAsset
}
if k.tokenRegistryKeeper.CheckEntryPermissions(rAsset, []tokenregistrytypes.Permission{tokenregistrytypes.Permission_DISABLE_BUY}) {
return nil, tokenregistrytypes.ErrNotAllowedToBuyAsset
}
// Get pool
var pool types.Pool
if types.StringCompare(msg.SentAsset.Symbol, types.NativeSymbol) {
pool, err = k.GetPool(ctx, msg.ReceivedAsset.Symbol)
} else {
pool, err = k.GetPool(ctx, msg.SentAsset.Symbol)
}
if err != nil {
return nil, err
}
// Check balances and fees
err = k.CheckPoolHealth(ctx, pool)
if err != nil {
return nil, err
}
expectedSwapFee := k.GetSwapFeeRate(ctx, *msg.SentAsset, false)
err = k.CheckSwapFee(ctx, pool, *msg.ReceivedAsset, false, expectedSwapFee)
if err != nil {
return nil, err
}
// Price impact
err = k.CheckPriceImpact(ctx, pool, *msg.ReceivedAsset, msg.SentAmount)
if err != nil {
return nil, err
}
// Execute swap
swapAmount, err := k.CLPCalcSwap(ctx, msg.SentAmount, *msg.ReceivedAsset, pool, false)
if err != nil {
return nil, err
}
// Final checks
err = k.CheckSentAmount(swapAmount, msg.MinReceivingAmount, *msg.ReceivedAsset)
if err != nil {
return nil, err
}
signer, err := sdk.AccAddressFromBech32(msg.Signer)
if err != nil {
return nil, err
}
// Finalize swap
err = k.ExecuteSwap(ctx, msg.SentAsset, msg.ReceivedAsset, msg.SentAmount, swapAmount, signer, pool)
if err != nil {
return nil, err
}
// Emit events
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeSwap,
sdk.NewAttribute(types.AttributeKeySentAmount, msg.SentAmount.String()),
sdk.NewAttribute(types.AttributeKeySentAsset, msg.SentAsset.Symbol),
sdk.NewAttribute(types.AttributeKeyReceivedAmount, swapAmount.String()),
sdk.NewAttribute(types.AttributeKeyPool, pool.String()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.Signer),
),
})
return &types.MsgSwapResponse{}, nil

var (
priceImpact sdk.Uint
)
Expand Down
2 changes: 2 additions & 0 deletions x/clp/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,5 @@ var (
ErrUnableToDistributeLPRewards = sdkerrors.Register(ModuleName, 50, "unable to distribute liquidity provider rewards")
ErrUnableToAddRewardAmountToLiquidityPool = sdkerrors.Register(ModuleName, 51, "unable to add reward amount to liquidity pool")
)
ErrSwapAmountTooSmall = sdkerrors.Register(ModuleName, 52, "swap amount is too small")
ErrPoolNotHealthy = sdkerrors.Register(ModuleName, 53, "pool is not healthy")