github.com/gravity-devs/liquidity@v1.5.3/x/liquidity/keeper/swap_test.go (about) 1 package keeper_test 2 3 import ( 4 "math/rand" 5 "testing" 6 7 sdk "github.com/cosmos/cosmos-sdk/types" 8 "github.com/stretchr/testify/require" 9 10 "github.com/gravity-devs/liquidity/app" 11 "github.com/gravity-devs/liquidity/x/liquidity" 12 "github.com/gravity-devs/liquidity/x/liquidity/types" 13 ) 14 15 func TestSimulationSwapExecutionFindEdgeCase(t *testing.T) { 16 for seed := int64(0); seed < 20; seed++ { 17 r := rand.New(rand.NewSource(seed)) 18 19 simapp, ctx := createTestInput() 20 params := simapp.LiquidityKeeper.GetParams(ctx) 21 22 // define test denom X, Y for Liquidity Pool 23 denomX := "denomX" 24 denomY := "denomY" 25 denomX, denomY = types.AlphabeticalDenomPair(denomX, denomY) 26 27 // get random X, Y amount for create pool 28 param := simapp.LiquidityKeeper.GetParams(ctx) 29 X, Y := app.GetRandPoolAmt(r, param.MinInitDepositAmount) 30 deposit := sdk.NewCoins(sdk.NewCoin(denomX, X), sdk.NewCoin(denomY, Y)) 31 32 // set pool creator account, balance for deposit 33 addrs := app.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) 34 app.SaveAccount(simapp, ctx, addrs[0], deposit) // pool creator 35 depositA := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomX) 36 depositB := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomY) 37 depositBalance := sdk.NewCoins(depositA, depositB) 38 require.Equal(t, deposit, depositBalance) 39 40 // create Liquidity pool 41 poolTypeID := types.DefaultPoolTypeID 42 msg := types.NewMsgCreatePool(addrs[0], poolTypeID, depositBalance) 43 _, err := simapp.LiquidityKeeper.CreatePool(ctx, msg) 44 require.NoError(t, err) 45 46 for i := 0; i < 20; i++ { 47 ctx = ctx.WithBlockHeight(int64(i)) 48 testSwapEdgeCases(t, r, simapp, ctx, X, Y, depositBalance, addrs) 49 } 50 } 51 } 52 53 func TestSwapExecution(t *testing.T) { 54 for seed := int64(0); seed < 50; seed++ { 55 s := rand.NewSource(seed) 56 r := rand.New(s) 57 simapp, ctx := createTestInput() 58 simapp.LiquidityKeeper.SetParams(ctx, types.DefaultParams()) 59 params := simapp.LiquidityKeeper.GetParams(ctx) 60 61 // define test denom X, Y for Liquidity Pool 62 denomX := "denomX" 63 denomY := "denomY" 64 denomX, denomY = types.AlphabeticalDenomPair(denomX, denomY) 65 66 // get random X, Y amount for create pool 67 X, Y := app.GetRandPoolAmt(r, params.MinInitDepositAmount) 68 deposit := sdk.NewCoins(sdk.NewCoin(denomX, X), sdk.NewCoin(denomY, Y)) 69 70 // set pool creator account, balance for deposit 71 addrs := app.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) 72 app.SaveAccount(simapp, ctx, addrs[0], deposit) // pool creator 73 depositA := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomX) 74 depositB := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomY) 75 depositBalance := sdk.NewCoins(depositA, depositB) 76 require.Equal(t, deposit, depositBalance) 77 78 // create Liquidity pool 79 poolTypeID := types.DefaultPoolTypeID 80 msg := types.NewMsgCreatePool(addrs[0], poolTypeID, depositBalance) 81 _, err := simapp.LiquidityKeeper.CreatePool(ctx, msg) 82 require.NoError(t, err) 83 84 // verify created liquidity pool 85 pools := simapp.LiquidityKeeper.GetAllPools(ctx) 86 poolID := pools[0].Id 87 require.Equal(t, 1, len(pools)) 88 require.Equal(t, uint64(1), poolID) 89 require.Equal(t, denomX, pools[0].ReserveCoinDenoms[0]) 90 require.Equal(t, denomY, pools[0].ReserveCoinDenoms[1]) 91 92 // verify minted pool coin 93 poolCoin := simapp.LiquidityKeeper.GetPoolCoinTotalSupply(ctx, pools[0]) 94 creatorBalance := simapp.BankKeeper.GetBalance(ctx, addrs[0], pools[0].PoolCoinDenom) 95 require.Equal(t, poolCoin, creatorBalance.Amount) 96 97 var xToY []*types.MsgSwapWithinBatch // buying Y from X 98 var yToX []*types.MsgSwapWithinBatch // selling Y for X 99 100 // make random orders, set buyer, seller accounts for the orders 101 xToY, yToX = app.GetRandomSizeOrders(denomX, denomY, X, Y, r, 250, 250) 102 buyerAddrs := app.AddTestAddrsIncremental(simapp, ctx, len(xToY), sdk.NewInt(0)) 103 sellerAddrs := app.AddTestAddrsIncremental(simapp, ctx, len(yToX), sdk.NewInt(0)) 104 105 for i, msg := range xToY { 106 app.SaveAccountWithFee(simapp, ctx, buyerAddrs[i], sdk.NewCoins(msg.OfferCoin), msg.OfferCoin) 107 msg.SwapRequesterAddress = buyerAddrs[i].String() 108 msg.PoolId = poolID 109 msg.OfferCoinFee = types.GetOfferCoinFee(msg.OfferCoin, params.SwapFeeRate) 110 } 111 for i, msg := range yToX { 112 app.SaveAccountWithFee(simapp, ctx, sellerAddrs[i], sdk.NewCoins(msg.OfferCoin), msg.OfferCoin) 113 msg.SwapRequesterAddress = sellerAddrs[i].String() 114 msg.PoolId = poolID 115 msg.OfferCoinFee = types.GetOfferCoinFee(msg.OfferCoin, params.SwapFeeRate) 116 } 117 118 // begin block, delete and init pool batch 119 liquidity.BeginBlocker(ctx, simapp.LiquidityKeeper) 120 121 // handle msgs, set order msgs to batch 122 for _, msg := range xToY { 123 _, err := simapp.LiquidityKeeper.SwapWithinBatch(ctx, msg, 0) 124 require.NoError(t, err) 125 } 126 for _, msg := range yToX { 127 _, err := simapp.LiquidityKeeper.SwapWithinBatch(ctx, msg, 0) 128 require.NoError(t, err) 129 } 130 131 // verify pool batch 132 liquidityPoolBatch, found := simapp.LiquidityKeeper.GetPoolBatch(ctx, poolID) 133 require.True(t, found) 134 require.NotNil(t, liquidityPoolBatch) 135 136 // end block, swap execution 137 liquidity.EndBlocker(ctx, simapp.LiquidityKeeper) 138 } 139 } 140 141 func testSwapEdgeCases(t *testing.T, r *rand.Rand, simapp *app.LiquidityApp, ctx sdk.Context, X, Y sdk.Int, depositBalance sdk.Coins, addrs []sdk.AccAddress) { 142 //simapp, ctx := createTestInput() 143 simapp.LiquidityKeeper.SetParams(ctx, types.DefaultParams()) 144 params := simapp.LiquidityKeeper.GetParams(ctx) 145 146 denomX := depositBalance[0].Denom 147 denomY := depositBalance[1].Denom 148 149 // verify created liquidity pool 150 pools := simapp.LiquidityKeeper.GetAllPools(ctx) 151 poolID := pools[0].Id 152 require.Equal(t, 1, len(pools)) 153 require.Equal(t, uint64(1), poolID) 154 require.Equal(t, denomX, pools[0].ReserveCoinDenoms[0]) 155 require.Equal(t, denomY, pools[0].ReserveCoinDenoms[1]) 156 157 // verify minted pool coin 158 poolCoin := simapp.LiquidityKeeper.GetPoolCoinTotalSupply(ctx, pools[0]) 159 creatorBalance := simapp.BankKeeper.GetBalance(ctx, addrs[0], pools[0].PoolCoinDenom) 160 require.Equal(t, poolCoin, creatorBalance.Amount) 161 162 var xToY []*types.MsgSwapWithinBatch // buying Y from X 163 var yToX []*types.MsgSwapWithinBatch // selling Y for X 164 165 batch, found := simapp.LiquidityKeeper.GetPoolBatch(ctx, poolID) 166 require.True(t, found) 167 168 remainingSwapMsgs := simapp.LiquidityKeeper.GetAllNotProcessedPoolBatchSwapMsgStates(ctx, batch) 169 if ctx.BlockHeight() == 0 || len(remainingSwapMsgs) == 0 { 170 // make random orders, set buyer, seller accounts for the orders 171 xToY, yToX = app.GetRandomSizeOrders(denomX, denomY, X, Y, r, 100, 100) 172 buyerAddrs := app.AddTestAddrsIncremental(simapp, ctx, len(xToY), sdk.NewInt(0)) 173 sellerAddrs := app.AddTestAddrsIncremental(simapp, ctx, len(yToX), sdk.NewInt(0)) 174 175 for i, msg := range xToY { 176 app.SaveAccountWithFee(simapp, ctx, buyerAddrs[i], sdk.NewCoins(msg.OfferCoin), msg.OfferCoin) 177 msg.SwapRequesterAddress = buyerAddrs[i].String() 178 msg.PoolId = poolID 179 msg.OfferCoinFee = types.GetOfferCoinFee(msg.OfferCoin, params.SwapFeeRate) 180 } 181 for i, msg := range yToX { 182 app.SaveAccountWithFee(simapp, ctx, sellerAddrs[i], sdk.NewCoins(msg.OfferCoin), msg.OfferCoin) 183 msg.SwapRequesterAddress = sellerAddrs[i].String() 184 msg.PoolId = poolID 185 msg.OfferCoinFee = types.GetOfferCoinFee(msg.OfferCoin, params.SwapFeeRate) 186 } 187 } 188 189 // begin block, delete and init pool batch 190 liquidity.BeginBlocker(ctx, simapp.LiquidityKeeper) 191 192 // handle msgs, set order msgs to batch 193 for _, msg := range xToY { 194 _, err := simapp.LiquidityKeeper.SwapWithinBatch(ctx, msg, int64(r.Intn(4))) 195 require.NoError(t, err) 196 } 197 for _, msg := range yToX { 198 _, err := simapp.LiquidityKeeper.SwapWithinBatch(ctx, msg, int64(r.Intn(4))) 199 require.NoError(t, err) 200 } 201 202 // verify pool batch 203 liquidityPoolBatch, found := simapp.LiquidityKeeper.GetPoolBatch(ctx, poolID) 204 require.True(t, found) 205 require.NotNil(t, liquidityPoolBatch) 206 207 // end block, swap execution 208 liquidity.EndBlocker(ctx, simapp.LiquidityKeeper) 209 } 210 211 func TestBadSwapExecution(t *testing.T) { 212 r := rand.New(rand.NewSource(0)) 213 214 simapp, ctx := app.CreateTestInput() 215 params := simapp.LiquidityKeeper.GetParams(ctx) 216 denomX, denomY := types.AlphabeticalDenomPair("denomX", "denomY") 217 218 // add pool creator account 219 X, Y := app.GetRandPoolAmt(r, params.MinInitDepositAmount) 220 deposit := sdk.NewCoins(sdk.NewCoin(denomX, X), sdk.NewCoin(denomY, Y)) 221 creatorAddr := app.AddRandomTestAddr(simapp, ctx, deposit.Add(params.PoolCreationFee...)) 222 balanceX := simapp.BankKeeper.GetBalance(ctx, creatorAddr, denomX) 223 balanceY := simapp.BankKeeper.GetBalance(ctx, creatorAddr, denomY) 224 creatorBalance := sdk.NewCoins(balanceX, balanceY) 225 require.Equal(t, deposit, creatorBalance) 226 227 // create pool 228 createPoolMsg := types.NewMsgCreatePool(creatorAddr, types.DefaultPoolTypeID, creatorBalance) 229 _, err := simapp.LiquidityKeeper.CreatePool(ctx, createPoolMsg) 230 require.NoError(t, err) 231 232 liquidity.BeginBlocker(ctx, simapp.LiquidityKeeper) 233 234 offerCoin := sdk.NewCoin(denomX, sdk.NewInt(10000)) 235 offerCoinFee := types.GetOfferCoinFee(offerCoin, params.SwapFeeRate) 236 testAddr := app.AddRandomTestAddr(simapp, ctx, sdk.NewCoins(offerCoin.Add(offerCoinFee))) 237 238 currentPrice := X.ToDec().Quo(Y.ToDec()) 239 swapMsg := types.NewMsgSwapWithinBatch(testAddr, 0, types.DefaultSwapTypeID, offerCoin, denomY, currentPrice, params.SwapFeeRate) 240 _, err = simapp.LiquidityKeeper.SwapWithinBatch(ctx, swapMsg, 0) 241 require.ErrorIs(t, err, types.ErrPoolNotExists) 242 243 liquidity.EndBlocker(ctx, simapp.LiquidityKeeper) 244 } 245 246 func TestBalancesAfterSwap(t *testing.T) { 247 for price := int64(9800); price < 10000; price++ { 248 simapp, ctx := app.CreateTestInput() 249 params := simapp.LiquidityKeeper.GetParams(ctx) 250 denomX, denomY := types.AlphabeticalDenomPair("denomx", "denomy") 251 X, Y := sdk.NewInt(100_000_000), sdk.NewInt(100_000_000) 252 253 creatorCoins := sdk.NewCoins(sdk.NewCoin(denomX, X), sdk.NewCoin(denomY, Y)) 254 creatorAddr := app.AddRandomTestAddr(simapp, ctx, creatorCoins.Add(params.PoolCreationFee...)) 255 256 orderPrice := sdk.NewDecWithPrec(price, 4) 257 aliceCoin := sdk.NewCoin(denomY, sdk.NewInt(10_000_000)) 258 aliceAddr := app.AddRandomTestAddr(simapp, ctx, sdk.NewCoins(aliceCoin)) 259 260 pool, err := simapp.LiquidityKeeper.CreatePool(ctx, types.NewMsgCreatePool(creatorAddr, types.DefaultPoolTypeID, creatorCoins)) 261 require.NoError(t, err) 262 263 liquidity.BeginBlocker(ctx, simapp.LiquidityKeeper) 264 265 offerAmt := aliceCoin.Amount.ToDec().Quo(sdk.MustNewDecFromStr("1.0015")).TruncateInt() 266 offerCoin := sdk.NewCoin(denomY, offerAmt) 267 268 _, err = simapp.LiquidityKeeper.SwapWithinBatch(ctx, types.NewMsgSwapWithinBatch( 269 aliceAddr, pool.Id, types.DefaultSwapTypeID, offerCoin, denomX, orderPrice, params.SwapFeeRate), 0) 270 require.NoError(t, err) 271 272 liquidity.EndBlocker(ctx, simapp.LiquidityKeeper) 273 274 deltaX := simapp.BankKeeper.GetBalance(ctx, aliceAddr, denomX).Amount 275 deltaY := simapp.BankKeeper.GetBalance(ctx, aliceAddr, denomY).Amount.Sub(aliceCoin.Amount) 276 require.Truef(t, !deltaX.IsNegative(), "deltaX should not be negative: %s", deltaX) 277 require.Truef(t, deltaY.IsNegative(), "deltaY should be negative: %s", deltaY) 278 279 deltaXWithoutFee := deltaX.ToDec().Quo(sdk.MustNewDecFromStr("0.9985")) 280 deltaYWithoutFee := deltaY.ToDec().Quo(sdk.MustNewDecFromStr("1.0015")) 281 effectivePrice := deltaXWithoutFee.Quo(deltaYWithoutFee.Neg()) 282 priceDiffRatio := orderPrice.Sub(effectivePrice).Abs().Quo(orderPrice) 283 require.Truef(t, priceDiffRatio.LT(sdk.MustNewDecFromStr("0.01")), "effectivePrice differs too much from orderPrice") 284 } 285 } 286 287 func TestRefundEscrow(t *testing.T) { 288 for seed := int64(0); seed < 100; seed++ { 289 r := rand.New(rand.NewSource(seed)) 290 291 X := sdk.NewInt(1_000_000) 292 Y := app.GetRandRange(r, 10_000_000_000_000_000, 1_000_000_000_000_000_000) 293 294 simapp, ctx := createTestInput() 295 params := simapp.LiquidityKeeper.GetParams(ctx) 296 297 addr := app.AddRandomTestAddr(simapp, ctx, sdk.NewCoins()) 298 299 pool, err := createPool(simapp, ctx, X, Y, DenomX, DenomY) 300 require.NoError(t, err) 301 302 for i := 0; i < 100; i++ { 303 poolBalances := simapp.BankKeeper.GetAllBalances(ctx, pool.GetReserveAccount()) 304 RX := poolBalances.AmountOf(DenomX) 305 RY := poolBalances.AmountOf(DenomY) 306 poolPrice := RX.ToDec().Quo(RY.ToDec()) 307 308 offerAmt := RY.ToDec().Mul(sdk.NewDecFromIntWithPrec(app.GetRandRange(r, 1, 100_000_000_000_000_000), sdk.Precision)) // RY * (0, 0.1) 309 offerAmtWithFee := offerAmt.Quo(sdk.OneDec().Add(params.SwapFeeRate.QuoInt64(2))).TruncateInt() // offerAmt / (1 + swapFeeRate/2) 310 orderPrice := poolPrice.Mul(sdk.NewDecFromIntWithPrec(app.GetRandRange(r, 1, 1_000_000_000_000_000_000), sdk.Precision)) // poolPrice * (0, 1) 311 312 app.SaveAccount(simapp, ctx, addr, sdk.NewCoins(sdk.NewCoin(DenomY, offerAmt.Ceil().TruncateInt()))) 313 314 liquidity.BeginBlocker(ctx, simapp.LiquidityKeeper) 315 316 _, err := simapp.LiquidityKeeper.SwapWithinBatch(ctx, types.NewMsgSwapWithinBatch( 317 addr, pool.Id, types.DefaultSwapTypeID, sdk.NewCoin(DenomY, offerAmtWithFee), DenomX, orderPrice, params.SwapFeeRate), 0) 318 require.NoError(t, err) 319 320 liquidity.EndBlocker(ctx, simapp.LiquidityKeeper) 321 } 322 323 require.True(t, simapp.BankKeeper.GetAllBalances(ctx, simapp.AccountKeeper.GetModuleAddress(types.ModuleName)).IsZero(), "there must be no remaining coin escrow") 324 } 325 } 326 327 func TestSwapWithDepletedPool(t *testing.T) { 328 simapp, ctx, pool, creatorAddr, err := createTestPool(sdk.NewInt64Coin(DenomX, 1000000), sdk.NewInt64Coin(DenomY, 1000000)) 329 require.NoError(t, err) 330 params := simapp.LiquidityKeeper.GetParams(ctx) 331 332 liquidity.BeginBlocker(ctx, simapp.LiquidityKeeper) 333 pc := simapp.BankKeeper.GetBalance(ctx, creatorAddr, pool.PoolCoinDenom) 334 _, err = simapp.LiquidityKeeper.WithdrawWithinBatch(ctx, types.NewMsgWithdrawWithinBatch(creatorAddr, pool.Id, pc)) 335 require.NoError(t, err) 336 liquidity.EndBlocker(ctx, simapp.LiquidityKeeper) 337 338 addr := app.AddRandomTestAddr(simapp, ctx, sdk.NewCoins(sdk.NewInt64Coin(DenomX, 100000))) 339 offerCoin := sdk.NewInt64Coin(DenomX, 10000) 340 orderPrice := sdk.MustNewDecFromStr("1.0") 341 liquidity.BeginBlocker(ctx, simapp.LiquidityKeeper) 342 _, err = simapp.LiquidityKeeper.SwapWithinBatch( 343 ctx, 344 types.NewMsgSwapWithinBatch(addr, pool.Id, types.DefaultSwapTypeID, offerCoin, DenomY, orderPrice, params.SwapFeeRate), 345 0) 346 require.ErrorIs(t, err, types.ErrDepletedPool) 347 liquidity.EndBlocker(ctx, simapp.LiquidityKeeper) 348 } 349 350 func createPool(simapp *app.LiquidityApp, ctx sdk.Context, X, Y sdk.Int, denomX, denomY string) (types.Pool, error) { 351 params := simapp.LiquidityKeeper.GetParams(ctx) 352 353 coins := sdk.NewCoins(sdk.NewCoin(denomX, X), sdk.NewCoin(denomY, Y)) 354 addr := app.AddRandomTestAddr(simapp, ctx, coins.Add(params.PoolCreationFee...)) 355 356 return simapp.LiquidityKeeper.CreatePool(ctx, types.NewMsgCreatePool(addr, types.DefaultPoolTypeID, coins)) 357 }