github.com/KiraCore/sekai@v0.3.43/x/basket/keeper/mint_burn_swap_test.go (about) 1 package keeper_test 2 3 import ( 4 "time" 5 6 "github.com/KiraCore/sekai/x/basket/types" 7 "github.com/cometbft/cometbft/crypto/ed25519" 8 sdk "github.com/cosmos/cosmos-sdk/types" 9 minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" 10 ) 11 12 func (suite *KeeperTestSuite) TestMintBasketToken() { 13 testCases := map[string]struct { 14 basketId uint64 15 mintDisabled bool 16 userBalance sdk.Coins 17 depositBalance sdk.Coins 18 denomDisabled bool 19 minDepositAmount sdk.Int 20 maxDepositAmount sdk.Int 21 limitPeriod uint64 22 prevDepositAmount sdk.Int 23 tokensCap sdk.Dec 24 expectErr bool 25 expectedOutAmount sdk.Int 26 }{ 27 "case not available basket": { 28 basketId: 0, 29 mintDisabled: false, 30 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000)), 31 depositBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000)), 32 denomDisabled: false, 33 minDepositAmount: sdk.NewInt(1000_000), 34 maxDepositAmount: sdk.NewInt(100_000_000), 35 limitPeriod: 3600, 36 prevDepositAmount: sdk.NewInt(0), 37 tokensCap: sdk.NewDec(1), 38 expectErr: true, 39 expectedOutAmount: sdk.NewInt(0), 40 }, 41 "case mint disabled basket": { 42 basketId: 1, 43 mintDisabled: true, 44 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000)), 45 depositBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000)), 46 denomDisabled: false, 47 minDepositAmount: sdk.NewInt(1000_000), 48 maxDepositAmount: sdk.NewInt(100_000_000), 49 limitPeriod: 3600, 50 prevDepositAmount: sdk.NewInt(0), 51 tokensCap: sdk.NewDec(1), 52 expectErr: true, 53 expectedOutAmount: sdk.NewInt(0), 54 }, 55 "case not enough balance on user": { 56 basketId: 1, 57 mintDisabled: true, 58 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 100_000)), 59 depositBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000)), 60 denomDisabled: false, 61 minDepositAmount: sdk.NewInt(1000_000), 62 maxDepositAmount: sdk.NewInt(100_000_000), 63 limitPeriod: 3600, 64 prevDepositAmount: sdk.NewInt(0), 65 tokensCap: sdk.NewDec(1), 66 expectErr: true, 67 expectedOutAmount: sdk.NewInt(0), 68 }, 69 "case not deposit denom": { 70 basketId: 1, 71 mintDisabled: false, 72 userBalance: sdk.NewCoins(sdk.NewInt64Coin("xxx", 1000_000)), 73 depositBalance: sdk.NewCoins(sdk.NewInt64Coin("xxx", 1000_000)), 74 denomDisabled: false, 75 minDepositAmount: sdk.NewInt(1000_000), 76 maxDepositAmount: sdk.NewInt(100_000_000), 77 limitPeriod: 3600, 78 prevDepositAmount: sdk.NewInt(0), 79 tokensCap: sdk.NewDec(1), 80 expectErr: true, 81 expectedOutAmount: sdk.NewInt(0), 82 }, 83 "case deposit denom disabled": { 84 basketId: 1, 85 mintDisabled: false, 86 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000)), 87 depositBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000)), 88 denomDisabled: true, 89 minDepositAmount: sdk.NewInt(1000_000), 90 maxDepositAmount: sdk.NewInt(100_000_000), 91 limitPeriod: 3600, 92 prevDepositAmount: sdk.NewInt(0), 93 tokensCap: sdk.NewDec(1), 94 expectErr: true, 95 expectedOutAmount: sdk.NewInt(0), 96 }, 97 "case lower than deposit min amount": { 98 basketId: 1, 99 mintDisabled: false, 100 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 100_000)), 101 depositBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 100_000)), 102 denomDisabled: false, 103 minDepositAmount: sdk.NewInt(1000_000), 104 maxDepositAmount: sdk.NewInt(100_000_000), 105 limitPeriod: 3600, 106 prevDepositAmount: sdk.NewInt(0), 107 tokensCap: sdk.NewDec(1), 108 expectErr: true, 109 expectedOutAmount: sdk.NewInt(0), 110 }, 111 "case exceeding deposit max amount during limit period": { 112 basketId: 1, 113 mintDisabled: false, 114 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 2000_000)), 115 depositBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 2000_000)), 116 denomDisabled: false, 117 minDepositAmount: sdk.NewInt(1000_000), 118 maxDepositAmount: sdk.NewInt(100_000_000), 119 limitPeriod: 3600, 120 prevDepositAmount: sdk.NewInt(99_000_000), 121 tokensCap: sdk.NewDec(1), 122 expectErr: true, 123 expectedOutAmount: sdk.NewInt(0), 124 }, 125 "case tokens cap is broken": { 126 basketId: 1, 127 mintDisabled: false, 128 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 2000_000)), 129 depositBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 2000_000)), 130 denomDisabled: false, 131 minDepositAmount: sdk.NewInt(1000_000), 132 maxDepositAmount: sdk.NewInt(100_000_000), 133 limitPeriod: 3600, 134 prevDepositAmount: sdk.NewInt(0), 135 tokensCap: sdk.NewDecWithPrec(8, 1), // 80% 136 expectErr: true, 137 expectedOutAmount: sdk.NewInt(0), 138 }, 139 "case one token successful deposit": { 140 basketId: 1, 141 mintDisabled: false, 142 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000)), 143 depositBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000)), 144 denomDisabled: false, 145 minDepositAmount: sdk.NewInt(1000_000), 146 maxDepositAmount: sdk.NewInt(100_000_000), 147 limitPeriod: 3600, 148 prevDepositAmount: sdk.NewInt(0), 149 tokensCap: sdk.NewDec(1), 150 expectErr: false, 151 expectedOutAmount: sdk.NewInt(1000_000), 152 }, 153 "case two tokens successful deposit": { 154 basketId: 1, 155 mintDisabled: false, 156 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000), sdk.NewInt64Coin("ueth", 100_000)), 157 depositBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000), sdk.NewInt64Coin("ueth", 100_000)), 158 denomDisabled: false, 159 minDepositAmount: sdk.NewInt(1000_000), 160 maxDepositAmount: sdk.NewInt(100_000_000), 161 limitPeriod: 3600, 162 prevDepositAmount: sdk.NewInt(0), 163 tokensCap: sdk.NewDec(1), 164 expectErr: false, 165 expectedOutAmount: sdk.NewInt(2000_000), 166 }, 167 } 168 169 for name, tc := range testCases { 170 tc := tc 171 172 suite.Run(name, func() { 173 suite.SetupTest() 174 now := time.Now().UTC() 175 suite.ctx = suite.ctx.WithBlockTime(now) 176 177 addr1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address().Bytes()) 178 basket := types.Basket{ 179 Id: 1, 180 Suffix: "usd", 181 Description: "usd basket", 182 LimitsPeriod: tc.limitPeriod, 183 Amount: sdk.NewInt(0), 184 SwapFee: sdk.NewDecWithPrec(1, 2), // 1% 185 SlipppageFeeMin: sdk.NewDecWithPrec(1, 2), // 1% 186 TokensCap: tc.tokensCap, 187 MintsMin: tc.minDepositAmount, 188 MintsMax: tc.maxDepositAmount, 189 MintsDisabled: tc.mintDisabled, 190 BurnsMin: sdk.NewInt(1), 191 BurnsMax: sdk.NewInt(100000000), 192 BurnsDisabled: false, 193 SwapsMin: sdk.NewInt(1), 194 SwapsMax: sdk.NewInt(100000000), 195 SwapsDisabled: false, 196 Tokens: []types.BasketToken{ 197 { 198 Denom: "ukex", 199 Weight: sdk.NewDec(1), 200 Amount: sdk.NewInt(0), 201 Deposits: !tc.denomDisabled, 202 Withdraws: !tc.denomDisabled, 203 Swaps: !tc.denomDisabled, 204 }, 205 { 206 Denom: "ueth", 207 Weight: sdk.NewDec(10), 208 Amount: sdk.NewInt(0), 209 Deposits: !tc.denomDisabled, 210 Withdraws: !tc.denomDisabled, 211 Swaps: !tc.denomDisabled, 212 }, 213 }, 214 Surplus: sdk.NewCoins(sdk.NewInt64Coin("usd2", 1)), 215 } 216 suite.app.BasketKeeper.SetBasket(suite.ctx, basket) 217 218 suite.app.BasketKeeper.SetMintAmount(suite.ctx, now.Add(time.Second*60), tc.basketId, tc.prevDepositAmount) 219 220 err := suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, tc.userBalance) 221 suite.Require().NoError(err) 222 err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addr1, tc.userBalance) 223 suite.Require().NoError(err) 224 225 err = suite.app.BasketKeeper.MintBasketToken(suite.ctx, &types.MsgBasketTokenMint{ 226 Sender: addr1.String(), 227 BasketId: tc.basketId, 228 Deposit: tc.depositBalance, 229 }) 230 if tc.expectErr { 231 suite.Require().Error(err) 232 } else { 233 suite.Require().NoError(err) 234 235 // check basket tokens balance increased 236 balance := suite.app.BankKeeper.GetBalance(suite.ctx, addr1, basket.GetBasketDenom()) 237 suite.Require().Equal(balance.Amount, tc.expectedOutAmount) 238 239 // check user's deposit balance decrease 240 balances := suite.app.BankKeeper.GetAllBalances(suite.ctx, addr1) 241 suite.Require().Equal(balances, sdk.Coins{balance}) 242 243 // check basket total amount increased 244 savedBasket, err := suite.app.BasketKeeper.GetBasketById(suite.ctx, basket.Id) 245 suite.Require().NoError(err) 246 suite.Require().Equal(savedBasket.Amount, tc.expectedOutAmount) 247 248 // check basket tokens balance increase 249 basketUnderlyingCoins := sdk.Coins{} 250 for _, token := range savedBasket.Tokens { 251 basketUnderlyingCoins = basketUnderlyingCoins.Add(sdk.NewCoin(token.Denom, token.Amount)) 252 } 253 suite.Require().Equal(basketUnderlyingCoins, tc.depositBalance) 254 255 // check limit period amount increased 256 historicalAmount := suite.app.BasketKeeper.GetLimitsPeriodMintAmount(suite.ctx, 1, tc.limitPeriod) 257 suite.Require().Equal(historicalAmount, tc.prevDepositAmount.Add(tc.expectedOutAmount)) 258 } 259 }) 260 } 261 } 262 263 func (suite *KeeperTestSuite) TestBurnBasketToken() { 264 sampleBasket := types.Basket{ 265 Id: 1, 266 Suffix: "usd", 267 Description: "usd basket", 268 } 269 basketDenom := sampleBasket.GetBasketDenom() 270 271 testCases := map[string]struct { 272 basketId uint64 273 burnDisabled bool 274 userBalance sdk.Coins 275 burnBalance sdk.Coin 276 denomDisabled bool 277 minBurnAmount sdk.Int 278 maxBurnAmount sdk.Int 279 limitPeriod uint64 280 prevBurnAmount sdk.Int 281 tokensCap sdk.Dec 282 expectErr bool 283 expectedOutAmount sdk.Coins 284 }{ 285 "case not available basket": { 286 basketId: 0, 287 burnDisabled: false, 288 userBalance: sdk.NewCoins(sdk.NewInt64Coin(basketDenom, 1000_000)), 289 burnBalance: sdk.NewInt64Coin(basketDenom, 1000_000), 290 denomDisabled: false, 291 minBurnAmount: sdk.NewInt(1000_000), 292 maxBurnAmount: sdk.NewInt(100_000_000), 293 limitPeriod: 3600, 294 prevBurnAmount: sdk.NewInt(0), 295 tokensCap: sdk.NewDec(1), 296 expectErr: true, 297 expectedOutAmount: sdk.NewCoins(), 298 }, 299 "case burn disabled basket": { 300 basketId: 1, 301 burnDisabled: true, 302 userBalance: sdk.NewCoins(sdk.NewInt64Coin(basketDenom, 1000_000)), 303 burnBalance: sdk.NewInt64Coin(basketDenom, 1000_000), 304 denomDisabled: false, 305 minBurnAmount: sdk.NewInt(1000_000), 306 maxBurnAmount: sdk.NewInt(100_000_000), 307 limitPeriod: 3600, 308 prevBurnAmount: sdk.NewInt(0), 309 tokensCap: sdk.NewDec(1), 310 expectErr: true, 311 expectedOutAmount: sdk.NewCoins(), 312 }, 313 "case not enough balance on user": { 314 basketId: 1, 315 burnDisabled: true, 316 userBalance: sdk.NewCoins(sdk.NewInt64Coin(basketDenom, 100_000)), 317 burnBalance: sdk.NewInt64Coin(basketDenom, 1000_000), 318 denomDisabled: false, 319 minBurnAmount: sdk.NewInt(1000_000), 320 maxBurnAmount: sdk.NewInt(100_000_000), 321 limitPeriod: 3600, 322 prevBurnAmount: sdk.NewInt(0), 323 tokensCap: sdk.NewDec(1), 324 expectErr: true, 325 expectedOutAmount: sdk.NewCoins(), 326 }, 327 "case not basket denom": { 328 basketId: 1, 329 burnDisabled: false, 330 userBalance: sdk.NewCoins(sdk.NewInt64Coin("xxx", 1000_000)), 331 burnBalance: sdk.NewInt64Coin("xxx", 1000_000), 332 denomDisabled: false, 333 minBurnAmount: sdk.NewInt(1000_000), 334 maxBurnAmount: sdk.NewInt(100_000_000), 335 limitPeriod: 3600, 336 prevBurnAmount: sdk.NewInt(0), 337 tokensCap: sdk.NewDec(1), 338 expectErr: true, 339 expectedOutAmount: sdk.NewCoins(), 340 }, 341 "case withdraw denom disabled": { 342 basketId: 1, 343 burnDisabled: false, 344 userBalance: sdk.NewCoins(sdk.NewInt64Coin(basketDenom, 1000_000)), 345 burnBalance: sdk.NewInt64Coin(basketDenom, 1000_000), 346 denomDisabled: true, 347 minBurnAmount: sdk.NewInt(1000_000), 348 maxBurnAmount: sdk.NewInt(100_000_000), 349 limitPeriod: 3600, 350 prevBurnAmount: sdk.NewInt(0), 351 tokensCap: sdk.NewDec(1), 352 expectErr: true, 353 expectedOutAmount: sdk.NewCoins(), 354 }, 355 "case lower than withdraw min amount": { 356 basketId: 1, 357 burnDisabled: false, 358 userBalance: sdk.NewCoins(sdk.NewInt64Coin(basketDenom, 100_000)), 359 burnBalance: sdk.NewInt64Coin(basketDenom, 100_000), 360 denomDisabled: false, 361 minBurnAmount: sdk.NewInt(1000_000), 362 maxBurnAmount: sdk.NewInt(100_000_000), 363 limitPeriod: 3600, 364 prevBurnAmount: sdk.NewInt(0), 365 tokensCap: sdk.NewDec(1), 366 expectErr: true, 367 expectedOutAmount: sdk.NewCoins(), 368 }, 369 "case exceeding withdraw max amount during limit period": { 370 basketId: 1, 371 burnDisabled: false, 372 userBalance: sdk.NewCoins(sdk.NewInt64Coin(basketDenom, 2000_000)), 373 burnBalance: sdk.NewInt64Coin(basketDenom, 2000_000), 374 denomDisabled: false, 375 minBurnAmount: sdk.NewInt(1000_000), 376 maxBurnAmount: sdk.NewInt(100_000_000), 377 limitPeriod: 3600, 378 prevBurnAmount: sdk.NewInt(99_000_000), 379 tokensCap: sdk.NewDec(1), 380 expectErr: true, 381 expectedOutAmount: sdk.NewCoins(), 382 }, 383 "case successful withdraw": { 384 basketId: 1, 385 burnDisabled: false, 386 userBalance: sdk.NewCoins(sdk.NewInt64Coin(basketDenom, 1000_000)), 387 burnBalance: sdk.NewInt64Coin(basketDenom, 1000_000), 388 denomDisabled: false, 389 minBurnAmount: sdk.NewInt(1000_000), 390 maxBurnAmount: sdk.NewInt(100_000_000), 391 limitPeriod: 3600, 392 prevBurnAmount: sdk.NewInt(0), 393 tokensCap: sdk.NewDec(1), 394 expectErr: false, 395 expectedOutAmount: sdk.NewCoins(sdk.NewInt64Coin("ukex", 500_000), sdk.NewInt64Coin("ueth", 50_000)), 396 }, 397 } 398 399 for name, tc := range testCases { 400 tc := tc 401 402 suite.Run(name, func() { 403 suite.SetupTest() 404 now := time.Now().UTC() 405 suite.ctx = suite.ctx.WithBlockTime(now) 406 407 addr1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address().Bytes()) 408 addr2 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address().Bytes()) 409 basket := types.Basket{ 410 Id: 1, 411 Suffix: "usd", 412 Description: "usd basket", 413 LimitsPeriod: tc.limitPeriod, 414 Amount: sdk.NewInt(0), 415 SwapFee: sdk.NewDecWithPrec(1, 2), // 1% 416 SlipppageFeeMin: sdk.NewDecWithPrec(1, 2), // 1% 417 TokensCap: tc.tokensCap, 418 MintsMin: sdk.NewInt(1), 419 MintsMax: sdk.NewInt(100000000), 420 MintsDisabled: false, 421 BurnsMin: tc.minBurnAmount, 422 BurnsMax: tc.maxBurnAmount, 423 BurnsDisabled: tc.burnDisabled, 424 SwapsMin: sdk.NewInt(1), 425 SwapsMax: sdk.NewInt(100000000), 426 SwapsDisabled: false, 427 Tokens: []types.BasketToken{ 428 { 429 Denom: "ukex", 430 Weight: sdk.NewDec(1), 431 Amount: sdk.NewInt(0), 432 Deposits: true, 433 Withdraws: !tc.denomDisabled, 434 Swaps: !tc.denomDisabled, 435 }, 436 { 437 Denom: "ueth", 438 Weight: sdk.NewDec(10), 439 Amount: sdk.NewInt(0), 440 Deposits: true, 441 Withdraws: !tc.denomDisabled, 442 Swaps: !tc.denomDisabled, 443 }, 444 }, 445 Surplus: sdk.NewCoins(sdk.NewInt64Coin("usd2", 1)), 446 } 447 suite.app.BasketKeeper.SetBasket(suite.ctx, basket) 448 449 depositCoins := sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000), sdk.NewInt64Coin("ueth", 100_000)) 450 err := suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, depositCoins) 451 suite.Require().NoError(err) 452 err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addr1, depositCoins) 453 suite.Require().NoError(err) 454 455 err = suite.app.BasketKeeper.MintBasketToken(suite.ctx, &types.MsgBasketTokenMint{ 456 Sender: addr1.String(), 457 BasketId: 1, 458 Deposit: depositCoins, 459 }) 460 suite.Require().NoError(err) 461 462 err = suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, tc.userBalance) 463 suite.Require().NoError(err) 464 err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addr2, tc.userBalance) 465 suite.Require().NoError(err) 466 467 suite.app.BasketKeeper.SetBurnAmount(suite.ctx, now.Add(time.Second*60), tc.basketId, tc.prevBurnAmount) 468 469 err = suite.app.BasketKeeper.BurnBasketToken(suite.ctx, &types.MsgBasketTokenBurn{ 470 Sender: addr2.String(), 471 BasketId: tc.basketId, 472 BurnAmount: tc.burnBalance, 473 }) 474 475 if tc.expectErr { 476 suite.Require().Error(err) 477 } else { 478 suite.Require().NoError(err) 479 480 // check basket tokens balance decreased 481 balance := suite.app.BankKeeper.GetBalance(suite.ctx, addr2, basket.GetBasketDenom()) 482 suite.Require().Equal(balance.Amount, sdk.ZeroInt()) 483 484 // check user's withdraw balance increase 485 balances := suite.app.BankKeeper.GetAllBalances(suite.ctx, addr2) 486 suite.Require().Equal(balances, tc.expectedOutAmount) 487 488 // check basket total amount increased 489 savedBasket, err := suite.app.BasketKeeper.GetBasketById(suite.ctx, basket.Id) 490 suite.Require().NoError(err) 491 suite.Require().Equal(savedBasket.Amount.Add(tc.burnBalance.Amount), sdk.NewInt(2000_000)) 492 493 // check basket tokens balance increase 494 basketUnderlyingCoins := sdk.Coins{} 495 for _, token := range savedBasket.Tokens { 496 basketUnderlyingCoins = basketUnderlyingCoins.Add(sdk.NewCoin(token.Denom, token.Amount)) 497 } 498 suite.Require().Equal(basketUnderlyingCoins.Add(tc.expectedOutAmount...).String(), depositCoins.String()) 499 500 // check limit period amount increased 501 historicalAmount := suite.app.BasketKeeper.GetLimitsPeriodBurnAmount(suite.ctx, 1, tc.limitPeriod) 502 suite.Require().Equal(historicalAmount, tc.prevBurnAmount.Add(tc.burnBalance.Amount)) 503 } 504 }) 505 } 506 } 507 508 func (suite *KeeperTestSuite) TestBasketSwap() { 509 testCases := map[string]struct { 510 basketId uint64 511 swapDisabled bool 512 userBalance sdk.Coins 513 swapBalance sdk.Coin 514 denomDisabled bool 515 minSwapAmount sdk.Int 516 maxSwapAmount sdk.Int 517 limitPeriod uint64 518 prevSwapAmount sdk.Int 519 tokensCap sdk.Dec 520 expectErr bool 521 expectedOutAmount sdk.Coins 522 }{ 523 "case not available basket": { 524 basketId: 0, 525 swapDisabled: false, 526 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000)), 527 swapBalance: sdk.NewInt64Coin("ukex", 100_000), 528 denomDisabled: false, 529 minSwapAmount: sdk.NewInt(100_000), 530 maxSwapAmount: sdk.NewInt(100_000_000), 531 limitPeriod: 3600, 532 prevSwapAmount: sdk.NewInt(0), 533 tokensCap: sdk.NewDec(1), 534 expectErr: true, 535 expectedOutAmount: sdk.NewCoins(), 536 }, 537 "case swap disabled basket": { 538 basketId: 1, 539 swapDisabled: true, 540 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000)), 541 swapBalance: sdk.NewInt64Coin("ukex", 100_000), 542 denomDisabled: false, 543 minSwapAmount: sdk.NewInt(100_000), 544 maxSwapAmount: sdk.NewInt(100_000_000), 545 limitPeriod: 3600, 546 prevSwapAmount: sdk.NewInt(0), 547 tokensCap: sdk.NewDec(1), 548 expectErr: true, 549 expectedOutAmount: sdk.NewCoins(), 550 }, 551 "case not enough balance on user": { 552 basketId: 1, 553 swapDisabled: true, 554 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 100)), 555 swapBalance: sdk.NewInt64Coin("ukex", 1000), 556 denomDisabled: false, 557 minSwapAmount: sdk.NewInt(100_000), 558 maxSwapAmount: sdk.NewInt(100_000_000), 559 limitPeriod: 3600, 560 prevSwapAmount: sdk.NewInt(0), 561 tokensCap: sdk.NewDec(1), 562 expectErr: true, 563 expectedOutAmount: sdk.NewCoins(), 564 }, 565 "case not basket denom": { 566 basketId: 1, 567 swapDisabled: false, 568 userBalance: sdk.NewCoins(sdk.NewInt64Coin("xxx", 100_000)), 569 swapBalance: sdk.NewInt64Coin("xxx", 100_000), 570 denomDisabled: false, 571 minSwapAmount: sdk.NewInt(100_000), 572 maxSwapAmount: sdk.NewInt(100_000_000), 573 limitPeriod: 3600, 574 prevSwapAmount: sdk.NewInt(0), 575 tokensCap: sdk.NewDec(1), 576 expectErr: true, 577 expectedOutAmount: sdk.NewCoins(), 578 }, 579 "case swap denom disabled": { 580 basketId: 1, 581 swapDisabled: false, 582 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 100_000)), 583 swapBalance: sdk.NewInt64Coin("ukex", 100_000), 584 denomDisabled: true, 585 minSwapAmount: sdk.NewInt(100_000), 586 maxSwapAmount: sdk.NewInt(100_000_000), 587 limitPeriod: 3600, 588 prevSwapAmount: sdk.NewInt(0), 589 tokensCap: sdk.NewDec(1), 590 expectErr: true, 591 expectedOutAmount: sdk.NewCoins(), 592 }, 593 "case lower than swap min amount": { 594 basketId: 1, 595 swapDisabled: false, 596 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 10_000)), 597 swapBalance: sdk.NewInt64Coin("ukex", 10_000), 598 denomDisabled: false, 599 minSwapAmount: sdk.NewInt(100_000), 600 maxSwapAmount: sdk.NewInt(100_000_000), 601 limitPeriod: 3600, 602 prevSwapAmount: sdk.NewInt(0), 603 tokensCap: sdk.NewDec(1), 604 expectErr: true, 605 expectedOutAmount: sdk.NewCoins(), 606 }, 607 "case exceeding swap max amount during limit period": { 608 basketId: 1, 609 swapDisabled: false, 610 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 2000_000)), 611 swapBalance: sdk.NewInt64Coin("ukex", 200_000), 612 denomDisabled: false, 613 minSwapAmount: sdk.NewInt(100_000), 614 maxSwapAmount: sdk.NewInt(100_000_000), 615 limitPeriod: 3600, 616 prevSwapAmount: sdk.NewInt(99_900_000), 617 tokensCap: sdk.NewDec(1), 618 expectErr: true, 619 expectedOutAmount: sdk.NewCoins(), 620 }, 621 "case tokens cap broken": { 622 basketId: 1, 623 swapDisabled: false, 624 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 500_000)), 625 swapBalance: sdk.NewInt64Coin("ukex", 500_000), 626 denomDisabled: false, 627 minSwapAmount: sdk.NewInt(100_000), 628 maxSwapAmount: sdk.NewInt(100_000_000), 629 limitPeriod: 3600, 630 prevSwapAmount: sdk.NewInt(99_000_000), 631 tokensCap: sdk.NewDecWithPrec(6, 1), // 60% 632 expectErr: true, 633 expectedOutAmount: sdk.NewCoins(), 634 }, 635 "case successful swap": { 636 basketId: 1, 637 swapDisabled: false, 638 userBalance: sdk.NewCoins(sdk.NewInt64Coin("ukex", 100_000)), 639 swapBalance: sdk.NewInt64Coin("ukex", 100_000), 640 denomDisabled: false, 641 minSwapAmount: sdk.NewInt(100_000), 642 maxSwapAmount: sdk.NewInt(100_000_000), 643 limitPeriod: 3600, 644 prevSwapAmount: sdk.NewInt(0), 645 tokensCap: sdk.NewDec(1), 646 expectErr: false, 647 expectedOutAmount: sdk.NewCoins(sdk.NewInt64Coin("ueth", 8_919)), 648 }, 649 } 650 651 for name, tc := range testCases { 652 tc := tc 653 654 suite.Run(name, func() { 655 suite.SetupTest() 656 now := time.Now().UTC() 657 suite.ctx = suite.ctx.WithBlockTime(now) 658 659 addr1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address().Bytes()) 660 addr2 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address().Bytes()) 661 basket := types.Basket{ 662 Id: 1, 663 Suffix: "usd", 664 Description: "usd basket", 665 LimitsPeriod: tc.limitPeriod, 666 Amount: sdk.NewInt(0), 667 SwapFee: sdk.NewDecWithPrec(1, 2), // 1% 668 SlipppageFeeMin: sdk.NewDecWithPrec(1, 2), // 1% 669 TokensCap: tc.tokensCap, 670 MintsMin: sdk.NewInt(1), 671 MintsMax: sdk.NewInt(100000000), 672 MintsDisabled: false, 673 BurnsMin: sdk.NewInt(1), 674 BurnsMax: sdk.NewInt(100000000), 675 BurnsDisabled: false, 676 SwapsMin: tc.minSwapAmount, 677 SwapsMax: tc.maxSwapAmount, 678 SwapsDisabled: tc.swapDisabled, 679 Tokens: []types.BasketToken{ 680 { 681 Denom: "ukex", 682 Weight: sdk.NewDec(1), 683 Amount: sdk.NewInt(0), 684 Deposits: true, 685 Withdraws: !tc.denomDisabled, 686 Swaps: !tc.denomDisabled, 687 }, 688 { 689 Denom: "ueth", 690 Weight: sdk.NewDec(10), 691 Amount: sdk.NewInt(0), 692 Deposits: true, 693 Withdraws: !tc.denomDisabled, 694 Swaps: !tc.denomDisabled, 695 }, 696 }, 697 Surplus: sdk.NewCoins(sdk.NewInt64Coin("usd2", 1)), 698 } 699 suite.app.BasketKeeper.SetBasket(suite.ctx, basket) 700 701 depositCoins := sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000), sdk.NewInt64Coin("ueth", 100_000)) 702 err := suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, depositCoins) 703 suite.Require().NoError(err) 704 err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addr1, depositCoins) 705 suite.Require().NoError(err) 706 707 err = suite.app.BasketKeeper.MintBasketToken(suite.ctx, &types.MsgBasketTokenMint{ 708 Sender: addr1.String(), 709 BasketId: 1, 710 Deposit: depositCoins, 711 }) 712 suite.Require().NoError(err) 713 714 err = suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, tc.userBalance) 715 suite.Require().NoError(err) 716 err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addr2, tc.userBalance) 717 suite.Require().NoError(err) 718 719 suite.app.BasketKeeper.SetSwapAmount(suite.ctx, now.Add(time.Second*60), tc.basketId, tc.prevSwapAmount) 720 721 err = suite.app.BasketKeeper.BasketSwap(suite.ctx, &types.MsgBasketTokenSwap{ 722 Sender: addr2.String(), 723 BasketId: tc.basketId, 724 Pairs: []types.SwapPair{ 725 { 726 InAmount: tc.swapBalance, 727 OutToken: "ueth", 728 }, 729 }, 730 }) 731 732 if tc.expectErr { 733 suite.Require().Error(err) 734 } else { 735 suite.Require().NoError(err) 736 737 // check in token balance decreased 738 balance := suite.app.BankKeeper.GetBalance(suite.ctx, addr2, "ukex") 739 suite.Require().Equal(balance.Amount, sdk.ZeroInt()) 740 741 // check user's withdraw balance increase 742 balances := suite.app.BankKeeper.GetAllBalances(suite.ctx, addr2) 743 suite.Require().Equal(balances.String(), tc.expectedOutAmount.String()) 744 745 // check basket total amount kept as it is 746 savedBasket, err := suite.app.BasketKeeper.GetBasketById(suite.ctx, basket.Id) 747 suite.Require().NoError(err) 748 suite.Require().Equal(savedBasket.Amount, sdk.NewInt(2000_000)) 749 750 // check basket tokens balance changes 751 basketUnderlyingCoins := sdk.Coins{} 752 for _, token := range savedBasket.Tokens { 753 basketUnderlyingCoins = basketUnderlyingCoins.Add(sdk.NewCoin(token.Denom, token.Amount)) 754 } 755 suite.Require().True(basketUnderlyingCoins.Add(tc.expectedOutAmount...).Sub(tc.swapBalance).IsAllLTE(depositCoins)) 756 757 // check limit period amount increased 758 historicalAmount := suite.app.BasketKeeper.GetLimitsPeriodSwapAmount(suite.ctx, 1, tc.limitPeriod) 759 suite.Require().Equal(historicalAmount, tc.prevSwapAmount.Add(tc.swapBalance.Amount)) 760 761 // check correct slippage amount + surplus 762 suite.Require().True(sdk.Coins(savedBasket.Surplus).Sub(basket.Surplus...).IsAllPositive()) 763 } 764 }) 765 } 766 } 767 768 func (suite *KeeperTestSuite) TestBasketWithdrawSurplus() { 769 suite.SetupTest() 770 now := time.Now().UTC() 771 suite.ctx = suite.ctx.WithBlockTime(now) 772 773 addr1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address().Bytes()) 774 addr2 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address().Bytes()) 775 basket := types.Basket{ 776 Id: 1, 777 Suffix: "usd", 778 Description: "usd basket", 779 LimitsPeriod: 3600, 780 Amount: sdk.NewInt(0), 781 SwapFee: sdk.NewDecWithPrec(1, 2), // 1% 782 SlipppageFeeMin: sdk.NewDecWithPrec(1, 2), // 1% 783 TokensCap: sdk.NewDecWithPrec(9, 1), // 90% 784 MintsMin: sdk.NewInt(1), 785 MintsMax: sdk.NewInt(100000000), 786 MintsDisabled: false, 787 BurnsMin: sdk.NewInt(1), 788 BurnsMax: sdk.NewInt(100000000), 789 BurnsDisabled: false, 790 SwapsMin: sdk.NewInt(1), 791 SwapsMax: sdk.NewInt(100000000), 792 SwapsDisabled: false, 793 Tokens: []types.BasketToken{ 794 { 795 Denom: "ukex", 796 Weight: sdk.NewDec(1), 797 Amount: sdk.NewInt(0), 798 Deposits: true, 799 Withdraws: true, 800 Swaps: true, 801 }, 802 { 803 Denom: "ueth", 804 Weight: sdk.NewDec(10), 805 Amount: sdk.NewInt(0), 806 Deposits: true, 807 Withdraws: true, 808 Swaps: true, 809 }, 810 }, 811 Surplus: sdk.NewCoins(sdk.NewInt64Coin("ueth", 1)), 812 } 813 suite.app.BasketKeeper.SetBasket(suite.ctx, basket) 814 815 depositCoins := sdk.NewCoins(sdk.NewInt64Coin("ukex", 1000_000), sdk.NewInt64Coin("ueth", 100_000)) 816 err := suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, depositCoins) 817 suite.Require().NoError(err) 818 err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addr1, depositCoins) 819 suite.Require().NoError(err) 820 821 err = suite.app.BasketKeeper.MintBasketToken(suite.ctx, &types.MsgBasketTokenMint{ 822 Sender: addr1.String(), 823 BasketId: 1, 824 Deposit: depositCoins, 825 }) 826 suite.Require().NoError(err) 827 828 err = suite.app.BasketKeeper.BasketWithdrawSurplus(suite.ctx, types.ProposalBasketWithdrawSurplus{ 829 BasketIds: []uint64{1}, 830 WithdrawTarget: addr2.String(), 831 }) 832 suite.Require().NoError(err) 833 834 // check account balance increased 835 balance := suite.app.BankKeeper.GetAllBalances(suite.ctx, addr2) 836 suite.Require().Equal(balance, sdk.Coins(basket.Surplus)) 837 838 // check surplus removal 839 savedBasket, err := suite.app.BasketKeeper.GetBasketById(suite.ctx, basket.Id) 840 suite.Require().NoError(err) 841 suite.Require().Nil(savedBasket.Surplus) 842 }