Skip to content

Commit

Permalink
add slippage on join/exit pool - oracle pool (#179)
Browse files Browse the repository at this point in the history
* basic code to add slippage on join/exit pool

* Resolve unit test on add/remove liquidity on oracle pool, and add more case on join pool

* Resolve comments on swap txs sorting mechanism PR
  • Loading branch information
jelysn authored Sep 1, 2023
1 parent c246d33 commit d132628
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 99 deletions.
66 changes: 5 additions & 61 deletions x/amm/keeper/abci.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package keeper

import (
"fmt"
"strings"
"time"

Expand All @@ -22,9 +21,8 @@ func (k Keeper) GetStackedSlippage(ctx sdk.Context, poolId uint64) sdk.Dec {
}

func (k Keeper) ApplySwapRequest(ctx sdk.Context, msg sdk.Msg) error {
switch msg.(type) {
switch msg := msg.(type) {
case *types.MsgSwapExactAmountIn:
msg := msg.(*types.MsgSwapExactAmountIn)
sender, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
return err
Expand All @@ -35,7 +33,6 @@ func (k Keeper) ApplySwapRequest(ctx sdk.Context, msg sdk.Msg) error {
}
return nil
case *types.MsgSwapExactAmountOut:
msg := msg.(*types.MsgSwapExactAmountOut)
sender, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
return err
Expand All @@ -46,17 +43,15 @@ func (k Keeper) ApplySwapRequest(ctx sdk.Context, msg sdk.Msg) error {
}
return nil
default:
return fmt.Errorf("unexpected swap message")
return types.ErrInvalidSwapMsgType
}
}

func (k Keeper) DeleteSwapRequest(ctx sdk.Context, msg sdk.Msg, index uint64) {
switch msg.(type) {
switch msg := msg.(type) {
case *types.MsgSwapExactAmountIn:
msg := msg.(*types.MsgSwapExactAmountIn)
k.DeleteSwapExactAmountInRequest(ctx, msg, index)
case *types.MsgSwapExactAmountOut:
msg := msg.(*types.MsgSwapExactAmountOut)
k.DeleteSwapExactAmountOutRequest(ctx, msg, index)
}
}
Expand All @@ -72,12 +67,10 @@ func (k Keeper) SelectOneSwapRequest(ctx sdk.Context, sprefix []byte) (sdk.Msg,

func (k Keeper) SelectReverseSwapRequest(ctx sdk.Context, msg sdk.Msg) (sdk.Msg, uint64) {
sprefix := []byte{}
switch msg.(type) {
switch msg := msg.(type) {
case *types.MsgSwapExactAmountIn:
msg := msg.(*types.MsgSwapExactAmountIn)
sprefix = types.TKeyPrefixSwapExactAmountInPrefix(msg)
case *types.MsgSwapExactAmountOut:
msg := msg.(*types.MsgSwapExactAmountOut)
sprefix = types.TKeyPrefixSwapExactAmountOutPrefix(msg)
}

Expand All @@ -90,12 +83,10 @@ func (k Keeper) SelectReverseSwapRequest(ctx sdk.Context, msg sdk.Msg) (sdk.Msg,
}

func (k Keeper) FirstPoolId(msg sdk.Msg) uint64 {
switch msg.(type) {
switch msg := msg.(type) {
case *types.MsgSwapExactAmountIn:
msg := msg.(*types.MsgSwapExactAmountIn)
return types.FirstPoolIdFromSwapExactAmountIn(msg)
case *types.MsgSwapExactAmountOut:
msg := msg.(*types.MsgSwapExactAmountOut)
return types.FirstPoolIdFromSwapExactAmountOut(msg)
}
return 0
Expand Down Expand Up @@ -176,51 +167,4 @@ func (k Keeper) EndBlocker(ctx sdk.Context) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)

k.ExecuteSwapRequests(ctx)
// swapInRequests := k.GetAllSwapExactAmountInRequests(ctx)
// for _, msg := range swapInRequests {
// sender, err := sdk.AccAddressFromBech32(msg.Sender)
// if err != nil {
// continue
// }

// cacheCtx, write := ctx.CacheContext()
// _, err = k.RouteExactAmountIn(cacheCtx, sender, msg.Routes, msg.TokenIn, math.Int(msg.TokenOutMinAmount))
// if err != nil {
// continue
// }
// write()

// // Swap event is handled elsewhere
// ctx.EventManager().EmitEvents(sdk.Events{
// sdk.NewEvent(
// sdk.EventTypeMessage,
// sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
// sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender),
// ),
// })

// }
// swapOutRequests := k.GetAllSwapExactAmountOutRequests(ctx)
// for _, msg := range swapOutRequests {
// sender, err := sdk.AccAddressFromBech32(msg.Sender)
// if err != nil {
// continue
// }

// cacheCtx, write := ctx.CacheContext()
// _, err = k.RouteExactAmountOut(cacheCtx, sender, msg.Routes, msg.TokenInMaxAmount, msg.TokenOut)
// if err != nil {
// continue
// }
// write()

// // Swap event is handled elsewhere
// ctx.EventManager().EmitEvents(sdk.Events{
// sdk.NewEvent(
// sdk.EventTypeMessage,
// sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
// sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender),
// ),
// })
// }
}
4 changes: 1 addition & 3 deletions x/amm/keeper/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,16 +276,14 @@ func (suite *KeeperTestSuite) TestExecuteSwapRequests() {

msgServer := keeper.NewMsgServerImpl(suite.app.AmmKeeper)
for _, msg := range tc.swapMsgs {
switch msg.(type) {
switch msg := msg.(type) {
case *types.MsgSwapExactAmountIn:
msg := msg.(*types.MsgSwapExactAmountIn)
_, err := msgServer.SwapExactAmountIn(
sdk.WrapSDKContext(suite.ctx),
msg,
)
suite.Require().NoError(err)
case *types.MsgSwapExactAmountOut:
msg := msg.(*types.MsgSwapExactAmountOut)
_, err := msgServer.SwapExactAmountOut(
sdk.WrapSDKContext(suite.ctx),
msg,
Expand Down
6 changes: 2 additions & 4 deletions x/amm/keeper/batch_processing.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ func (k Keeper) GetLastSwapRequestIndex(ctx sdk.Context) uint64 {
}

// SetSwapExactAmountInRequests stores swap exact amount in request
func (k Keeper) SetSwapExactAmountInRequests(ctx sdk.Context, msg *types.MsgSwapExactAmountIn, index uint64) error {
func (k Keeper) SetSwapExactAmountInRequests(ctx sdk.Context, msg *types.MsgSwapExactAmountIn, index uint64) {
store := prefix.NewStore(ctx.TransientStore(k.transientStoreKey), types.KeyPrefix(types.TSwapExactAmountInKey))
b := k.cdc.MustMarshal(msg)
store.Set(types.TKeyPrefixSwapExactAmountIn(msg, index), b)
return nil
}

// DeleteSwapExactAmountInRequest removes a swap exact amount in request
Expand Down Expand Up @@ -65,11 +64,10 @@ func (k Keeper) GetFirstSwapExactAmountInRequest(ctx sdk.Context, sprefix []byte
}

// SetSwapExactAmountInRequests stores swap exact amount out request
func (k Keeper) SetSwapExactAmountOutRequests(ctx sdk.Context, msg *types.MsgSwapExactAmountOut, index uint64) error {
func (k Keeper) SetSwapExactAmountOutRequests(ctx sdk.Context, msg *types.MsgSwapExactAmountOut, index uint64) {
store := prefix.NewStore(ctx.TransientStore(k.transientStoreKey), types.KeyPrefix(types.TSwapExactAmountOutKey))
b := k.cdc.MustMarshal(msg)
store.Set(types.TKeyPrefixSwapExactAmountOut(msg, index), b)
return nil
}

// DeleteSwapExactAmountOutRequest deletes a swap exact amount out request
Expand Down
8 changes: 4 additions & 4 deletions x/amm/keeper/msg_server_exit_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ func (suite *KeeperTestSuite) TestMsgServerExitPool() {
},
shareInAmount: types.OneShare.Quo(sdk.NewInt(10)),
tokenOutDenom: "uusdt",
minAmountsOut: sdk.Coins{sdk.NewInt64Coin("uusdt", 97368)},
expSenderBalance: sdk.Coins{sdk.NewInt64Coin("uusdt", 97368)},
minAmountsOut: sdk.Coins{sdk.NewInt64Coin("uusdt", 95114)},
expSenderBalance: sdk.Coins{sdk.NewInt64Coin("uusdt", 95114)},
expPass: true,
},
{
Expand All @@ -100,8 +100,8 @@ func (suite *KeeperTestSuite) TestMsgServerExitPool() {
},
shareInAmount: types.OneShare.Quo(sdk.NewInt(10)),
tokenOutDenom: "uusdc",
minAmountsOut: sdk.Coins{sdk.NewInt64Coin("uusdc", 100000)},
expSenderBalance: sdk.Coins{sdk.NewInt64Coin("uusdc", 100000)},
minAmountsOut: sdk.Coins{sdk.NewInt64Coin("uusdc", 99197)},
expSenderBalance: sdk.Coins{sdk.NewInt64Coin("uusdc", 99197)},
expPass: true,
},
} {
Expand Down
25 changes: 23 additions & 2 deletions x/amm/keeper/msg_server_join_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (suite *KeeperTestSuite) TestMsgServerJoinPool() {
ThresholdWeightDifference: sdk.NewDecWithPrec(2, 1), // 20%
FeeDenom: "uusdc",
},
shareOutAmount: sdk.NewInt(833333333333333333), // weight breaking fee
shareOutAmount: sdk.NewInt(694444166666666666), // weight breaking fee
expSenderBalance: sdk.Coins{},
expTokenIn: sdk.Coins{sdk.NewInt64Coin("uusdt", 1000000)},
expPass: true,
Expand All @@ -99,11 +99,32 @@ func (suite *KeeperTestSuite) TestMsgServerJoinPool() {
ThresholdWeightDifference: sdk.NewDecWithPrec(2, 1), // 20%
FeeDenom: "uusdc",
},
shareOutAmount: sdk.NewInt(1250000000000000000), // weight breaking fee
shareOutAmount: sdk.NewInt(805987500000000000), // weight recovery direction
expSenderBalance: sdk.Coins{},
expTokenIn: sdk.Coins{sdk.NewInt64Coin("uusdt", 1000000)},
expPass: true,
},
{
desc: "oracle pool join - zero slippage add liquidity",
senderInitBalance: sdk.Coins{sdk.NewInt64Coin("uusdc", 1500000), sdk.NewInt64Coin("uusdt", 500000)},
poolInitBalance: sdk.Coins{sdk.NewInt64Coin("uusdc", 1500000), sdk.NewInt64Coin("uusdt", 500000)},
poolParams: types.PoolParams{
SwapFee: sdk.ZeroDec(),
ExitFee: sdk.ZeroDec(),
UseOracle: true,
WeightBreakingFeeMultiplier: sdk.NewDecWithPrec(1, 0), // 1.00
ExternalLiquidityRatio: sdk.NewDec(1),
LpFeePortion: sdk.ZeroDec(),
StakingFeePortion: sdk.ZeroDec(),
WeightRecoveryFeePortion: sdk.ZeroDec(),
ThresholdWeightDifference: sdk.NewDecWithPrec(2, 1), // 20%
FeeDenom: "uusdc",
},
shareOutAmount: sdk.NewInt(2000000000000000000),
expSenderBalance: sdk.Coins{},
expTokenIn: sdk.Coins{sdk.NewInt64Coin("uusdc", 1500000), sdk.NewInt64Coin("uusdt", 500000)},
expPass: true,
},
} {
suite.Run(tc.desc, func() {
suite.SetupTest()
Expand Down
66 changes: 63 additions & 3 deletions x/amm/types/calc_exit_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,66 @@ import (
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)

func CalcExitValueWithoutSlippage(ctx sdk.Context, oracleKeeper OracleKeeper, pool Pool, exitingShares sdk.Int, tokenOutDenom string) (sdk.Dec, error) {
tvl, err := pool.TVL(ctx, oracleKeeper)
if err != nil {
return sdk.ZeroDec(), err
}

totalShares := pool.GetTotalShares()
var refundedShares sdk.Dec
refundedShares = sdk.NewDecFromInt(exitingShares)
exitValue := tvl.Mul(refundedShares).Quo(sdk.NewDecFromInt(totalShares.Amount))

if exitingShares.GTE(totalShares.Amount) {
return sdk.ZeroDec(), sdkerrors.Wrapf(ErrLimitMaxAmount, ErrMsgFormatSharesLargerThanMax, exitingShares, totalShares)
}

shareOutRatio := refundedShares.QuoInt(totalShares.Amount)
// exitedCoins = shareOutRatio * pool liquidity
exitedCoins := sdk.Coins{}
poolLiquidity := pool.GetTotalPoolLiquidity()

for _, asset := range poolLiquidity {
// round down here, due to not wanting to over-exit
exitAmt := shareOutRatio.MulInt(asset.Amount).TruncateInt()
if exitAmt.LTE(sdk.ZeroInt()) {
continue
}
if exitAmt.GTE(asset.Amount) {
return sdk.ZeroDec(), errors.New("too many shares out")
}
exitedCoins = exitedCoins.Add(sdk.NewCoin(asset.Denom, exitAmt))
}

slippageValue := sdk.ZeroDec()
for _, exitedCoin := range exitedCoins {
if exitedCoin.Denom == tokenOutDenom {
continue
}
inTokenPrice := oracleKeeper.GetAssetPriceFromDenom(ctx, exitedCoin.Denom)
if inTokenPrice.IsZero() {
return sdk.ZeroDec(), fmt.Errorf("token price not set: %s", exitedCoin.Denom)
}
resizedAmount := sdk.NewDecFromInt(exitedCoin.Amount).
Quo(pool.PoolParams.ExternalLiquidityRatio).RoundInt()
slippageAmount, err := pool.CalcGivenInSlippage(
ctx,
oracleKeeper,
&pool,
sdk.Coins{sdk.NewCoin(exitedCoin.Denom, resizedAmount)},
tokenOutDenom,
)
if err != nil {
return sdk.ZeroDec(), err
}

slippageValue = slippageValue.Add(slippageAmount.Mul(inTokenPrice))
}
exitValueWithoutSlippage := exitValue.Sub(slippageValue)
return exitValueWithoutSlippage, nil
}

// CalcExitPool returns how many tokens should come out, when exiting k LP shares against a "standard" CFMM
func CalcExitPool(ctx sdk.Context, oracleKeeper OracleKeeper, pool Pool, exitingShares sdk.Int, tokenOutDenom string) (sdk.Coins, error) {
totalShares := pool.GetTotalShares()
Expand All @@ -27,13 +87,13 @@ func CalcExitPool(ctx sdk.Context, oracleKeeper OracleKeeper, pool Pool, exiting

if pool.PoolParams.UseOracle && tokenOutDenom != "" {
initialWeightDistance := pool.WeightDistanceFromTarget(ctx, oracleKeeper, pool.PoolAssets)
tvl, err := pool.TVL(ctx, oracleKeeper)
tokenPrice := oracleKeeper.GetAssetPriceFromDenom(ctx, tokenOutDenom)
exitValueWithoutSlippage, err := CalcExitValueWithoutSlippage(ctx, oracleKeeper, pool, exitingShares, tokenOutDenom)
if err != nil {
return sdk.Coins{}, err
}

tokenPrice := oracleKeeper.GetAssetPriceFromDenom(ctx, tokenOutDenom)
oracleOutAmount := tvl.Mul(refundedShares).Quo(sdk.NewDecFromInt(totalShares.Amount)).Quo(tokenPrice)
oracleOutAmount := exitValueWithoutSlippage.Quo(tokenPrice)

newAssetPools, err := pool.NewPoolAssetsAfterSwap(
sdk.Coins{},
Expand Down
3 changes: 2 additions & 1 deletion x/amm/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ var (

ErrTooManyTokensOut = sdkerrors.Register(ModuleName, 31, "tx is trying to get more tokens out of the pool than exist")

ErrInvalidPoolId = sdkerrors.Register(ModuleName, 91, "invalid pool id")
ErrInvalidPoolId = sdkerrors.Register(ModuleName, 91, "invalid pool id")
ErrInvalidSwapMsgType = sdkerrors.Register(ModuleName, 92, "unexpected swap message type")
)

const (
Expand Down
16 changes: 6 additions & 10 deletions x/amm/types/key_batch_txs.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
package types

import (
"encoding/binary"
fmt "fmt"
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"
)

var _ binary.ByteOrder

const (
TLastSwapRequestIndex = "last-swap-request-index"
TSwapExactAmountInKey = "batch/swap-exact-amount-in"
TSwapExactAmountOutKey = "batch/swap-exact-amount-out"
)

func TKeyPrefixSwapExactAmountInPrefix(m *MsgSwapExactAmountIn) []byte {
prefix := []byte(m.TokenIn.Denom + "/")
prefix := []byte(fmt.Sprintf("%s/", m.TokenIn.Denom))
routeKeys := []string{}
for _, route := range m.Routes[:1] {
routeKeys = append(routeKeys, fmt.Sprintf("%d/%s", route.PoolId, route.TokenOutDenom))
Expand All @@ -27,8 +24,8 @@ func TKeyPrefixSwapExactAmountInPrefix(m *MsgSwapExactAmountIn) []byte {
}

func FirstPoolIdFromSwapExactAmountIn(m *MsgSwapExactAmountIn) uint64 {
for _, route := range m.Routes {
return route.PoolId
if len(m.Routes) > 0 {
return m.Routes[0].PoolId
}
return 0
}
Expand All @@ -39,12 +36,11 @@ func TKeyPrefixSwapExactAmountIn(m *MsgSwapExactAmountIn, index uint64) []byte {
}

func TKeyPrefixSwapExactAmountOutPrefix(m *MsgSwapExactAmountOut) []byte {
prefix := []byte("/" + m.TokenOut.Denom)
prefix := []byte(fmt.Sprintf("/%s", m.TokenOut.Denom))
routeKeys := []string{}
for i := len(m.Routes) - 1; i >= 0; i-- {
route := m.Routes[i]
if len(m.Routes) > 0 {
route := m.Routes[len(m.Routes)-1]
routeKeys = append(routeKeys, fmt.Sprintf("%s/%d", route.TokenInDenom, route.PoolId))
break
}
prefix = append([]byte(strings.Join(routeKeys, "/")), prefix...)
return prefix
Expand Down
Loading

0 comments on commit d132628

Please sign in to comment.