package keeper_test import ( "github.com/kava-labs/kava/x/swap/types" "github.com/kava-labs/kava/x/swap/types/mocks" sdk "github.com/cosmos/cosmos-sdk/types" sdkmath "cosmossdk.io/math" "github.com/stretchr/testify/mock" ) func (suite *keeperTestSuite) TestHooks_DepositAndWithdraw() { suite.Keeper.ClearHooks() swapHooks := &mocks.SwapHooks{} suite.Keeper.SetHooks(swapHooks) pool := types.NewAllowedPool("ukava", "usdx") suite.Require().NoError(pool.Validate()) suite.Keeper.SetParams(suite.Ctx, types.NewParams(types.NewAllowedPools(pool), types.DefaultSwapFee)) balance := sdk.NewCoins( sdk.NewCoin(pool.TokenA, sdkmath.NewInt(1000e6)), sdk.NewCoin(pool.TokenB, sdkmath.NewInt(1000e6)), ) depositor_1 := suite.CreateAccount(balance) depositA := sdk.NewCoin(pool.TokenA, sdkmath.NewInt(10e6)) depositB := sdk.NewCoin(pool.TokenB, sdkmath.NewInt(50e6)) deposit := sdk.NewCoins(depositA, depositB) // expected initial shares - geometric mean expectedShares := sdkmath.NewInt(22360679) // first deposit creates pool - calls AfterPoolDepositCreated with initial shares swapHooks.On("AfterPoolDepositCreated", suite.Ctx, types.PoolIDFromCoins(deposit), depositor_1.GetAddress(), expectedShares).Once() err := suite.Keeper.Deposit(suite.Ctx, depositor_1.GetAddress(), depositA, depositB, sdk.MustNewDecFromStr("0.0015")) suite.Require().NoError(err) // second deposit adds to pool - calls BeforePoolDepositModified // shares given are the initial shares, not the shares added to the pool swapHooks.On("BeforePoolDepositModified", suite.Ctx, types.PoolIDFromCoins(deposit), depositor_1.GetAddress(), expectedShares).Once() err = suite.Keeper.Deposit(suite.Ctx, depositor_1.GetAddress(), sdk.NewCoin("ukava", sdkmath.NewInt(5e6)), sdk.NewCoin("usdx", sdkmath.NewInt(25e6)), sdk.MustNewDecFromStr("0.0015")) suite.Require().NoError(err) // get the shares from the store from the last deposit shareRecord, found := suite.Keeper.GetDepositorShares(suite.Ctx, depositor_1.GetAddress(), types.PoolIDFromCoins(deposit)) suite.Require().True(found) // third deposit adds to pool - calls BeforePoolDepositModified // shares given are the shares added in previous deposit, not the shares added to the pool now swapHooks.On("BeforePoolDepositModified", suite.Ctx, types.PoolIDFromCoins(deposit), depositor_1.GetAddress(), shareRecord.SharesOwned).Once() err = suite.Keeper.Deposit(suite.Ctx, depositor_1.GetAddress(), sdk.NewCoin("ukava", sdkmath.NewInt(10e6)), sdk.NewCoin("usdx", sdkmath.NewInt(50e6)), sdk.MustNewDecFromStr("0.0015")) suite.Require().NoError(err) depositor_2 := suite.NewAccountFromAddr( sdk.AccAddress("depositor 2---------"), sdk.NewCoins( sdk.NewCoin("ukava", sdkmath.NewInt(100e6)), sdk.NewCoin("usdx", sdkmath.NewInt(100e6)), ), ) // first deposit deposit into pool creates the deposit and calls AfterPoolDepositCreated expectedShares = sdkmath.NewInt(2236067) swapHooks.On("AfterPoolDepositCreated", suite.Ctx, types.PoolIDFromCoins(deposit), depositor_2.GetAddress(), expectedShares).Once() err = suite.Keeper.Deposit(suite.Ctx, depositor_2.GetAddress(), sdk.NewCoin("ukava", sdkmath.NewInt(1e6)), sdk.NewCoin("usdx", sdkmath.NewInt(5e6)), sdk.MustNewDecFromStr("0.0015")) suite.Require().NoError(err) // second deposit into pool calls BeforePoolDepositModified with initial shares given swapHooks.On("BeforePoolDepositModified", suite.Ctx, types.PoolIDFromCoins(deposit), depositor_2.GetAddress(), expectedShares).Once() err = suite.Keeper.Deposit(suite.Ctx, depositor_2.GetAddress(), sdk.NewCoin("ukava", sdkmath.NewInt(2e6)), sdk.NewCoin("usdx", sdkmath.NewInt(10e6)), sdk.MustNewDecFromStr("0.0015")) suite.Require().NoError(err) // get the shares from the store from the last deposit shareRecord, found = suite.Keeper.GetDepositorShares(suite.Ctx, depositor_2.GetAddress(), types.PoolIDFromCoins(deposit)) suite.Require().True(found) // third deposit into pool calls BeforePoolDepositModified with shares from last deposit swapHooks.On("BeforePoolDepositModified", suite.Ctx, types.PoolIDFromCoins(deposit), depositor_2.GetAddress(), shareRecord.SharesOwned).Once() err = suite.Keeper.Deposit(suite.Ctx, depositor_2.GetAddress(), sdk.NewCoin("ukava", sdkmath.NewInt(3e6)), sdk.NewCoin("usdx", sdkmath.NewInt(15e6)), sdk.MustNewDecFromStr("0.0015")) suite.Require().NoError(err) // test hooks with a full withdraw of all shares shareRecord, found = suite.Keeper.GetDepositorShares(suite.Ctx, depositor_1.GetAddress(), types.PoolIDFromCoins(deposit)) suite.Require().True(found) // all shares given to BeforePoolDepositModified swapHooks.On("BeforePoolDepositModified", suite.Ctx, types.PoolIDFromCoins(deposit), depositor_1.GetAddress(), shareRecord.SharesOwned).Once() err = suite.Keeper.Withdraw(suite.Ctx, depositor_1.GetAddress(), shareRecord.SharesOwned, sdk.NewCoin("ukava", sdkmath.NewInt(1)), sdk.NewCoin("usdx", sdkmath.NewInt(1))) suite.Require().NoError(err) // test hooks on partial withdraw shareRecord, found = suite.Keeper.GetDepositorShares(suite.Ctx, depositor_2.GetAddress(), types.PoolIDFromCoins(deposit)) suite.Require().True(found) partialShares := shareRecord.SharesOwned.Quo(sdkmath.NewInt(3)) // all shares given to before deposit modified even with partial withdraw swapHooks.On("BeforePoolDepositModified", suite.Ctx, types.PoolIDFromCoins(deposit), depositor_2.GetAddress(), shareRecord.SharesOwned).Once() err = suite.Keeper.Withdraw(suite.Ctx, depositor_2.GetAddress(), partialShares, sdk.NewCoin("ukava", sdkmath.NewInt(1)), sdk.NewCoin("usdx", sdkmath.NewInt(1))) suite.Require().NoError(err) // test hooks on second partial withdraw shareRecord, found = suite.Keeper.GetDepositorShares(suite.Ctx, depositor_2.GetAddress(), types.PoolIDFromCoins(deposit)) suite.Require().True(found) partialShares = shareRecord.SharesOwned.Quo(sdkmath.NewInt(2)) // all shares given to before deposit modified even with partial withdraw swapHooks.On("BeforePoolDepositModified", suite.Ctx, types.PoolIDFromCoins(deposit), depositor_2.GetAddress(), shareRecord.SharesOwned).Once() err = suite.Keeper.Withdraw(suite.Ctx, depositor_2.GetAddress(), partialShares, sdk.NewCoin("ukava", sdkmath.NewInt(1)), sdk.NewCoin("usdx", sdkmath.NewInt(1))) suite.Require().NoError(err) // test hooks withdraw all shares with second depositor shareRecord, found = suite.Keeper.GetDepositorShares(suite.Ctx, depositor_2.GetAddress(), types.PoolIDFromCoins(deposit)) suite.Require().True(found) // all shares given to before deposit modified even with partial withdraw swapHooks.On("BeforePoolDepositModified", suite.Ctx, types.PoolIDFromCoins(deposit), depositor_2.GetAddress(), shareRecord.SharesOwned).Once() err = suite.Keeper.Withdraw(suite.Ctx, depositor_2.GetAddress(), shareRecord.SharesOwned, sdk.NewCoin("ukava", sdkmath.NewInt(1)), sdk.NewCoin("usdx", sdkmath.NewInt(1))) suite.Require().NoError(err) swapHooks.AssertExpectations(suite.T()) } func (suite *keeperTestSuite) TestHooks_NoPanicsOnNilHooks() { suite.Keeper.ClearHooks() pool := types.NewAllowedPool("ukava", "usdx") suite.Require().NoError(pool.Validate()) suite.Keeper.SetParams(suite.Ctx, types.NewParams(types.NewAllowedPools(pool), types.DefaultSwapFee)) balance := sdk.NewCoins( sdk.NewCoin(pool.TokenA, sdkmath.NewInt(1000e6)), sdk.NewCoin(pool.TokenB, sdkmath.NewInt(1000e6)), ) depositor := suite.CreateAccount(balance) depositA := sdk.NewCoin(pool.TokenA, sdkmath.NewInt(10e6)) depositB := sdk.NewCoin(pool.TokenB, sdkmath.NewInt(50e6)) deposit := sdk.NewCoins(depositA, depositB) // deposit create pool should not panic when hooks are not set err := suite.Keeper.Deposit(suite.Ctx, depositor.GetAddress(), depositA, depositB, sdk.MustNewDecFromStr("0.0015")) suite.Require().NoError(err) // existing deposit should not panic with hooks are not set err = suite.Keeper.Deposit(suite.Ctx, depositor.GetAddress(), sdk.NewCoin("ukava", sdkmath.NewInt(5e6)), sdk.NewCoin("usdx", sdkmath.NewInt(25e6)), sdk.MustNewDecFromStr("0.0015")) suite.Require().NoError(err) // withdraw of shares should not panic when hooks are not set shareRecord, found := suite.Keeper.GetDepositorShares(suite.Ctx, depositor.GetAddress(), types.PoolIDFromCoins(deposit)) suite.Require().True(found) err = suite.Keeper.Withdraw(suite.Ctx, depositor.GetAddress(), shareRecord.SharesOwned, sdk.NewCoin("ukava", sdkmath.NewInt(1)), sdk.NewCoin("usdx", sdkmath.NewInt(1))) suite.Require().NoError(err) } func (suite *keeperTestSuite) TestHooks_HookOrdering() { suite.Keeper.ClearHooks() swapHooks := &mocks.SwapHooks{} suite.Keeper.SetHooks(swapHooks) pool := types.NewAllowedPool("ukava", "usdx") suite.Require().NoError(pool.Validate()) suite.Keeper.SetParams(suite.Ctx, types.NewParams(types.NewAllowedPools(pool), types.DefaultSwapFee)) balance := sdk.NewCoins( sdk.NewCoin(pool.TokenA, sdkmath.NewInt(1000e6)), sdk.NewCoin(pool.TokenB, sdkmath.NewInt(1000e6)), ) depositor := suite.CreateAccount(balance) depositA := sdk.NewCoin(pool.TokenA, sdkmath.NewInt(10e6)) depositB := sdk.NewCoin(pool.TokenB, sdkmath.NewInt(50e6)) deposit := sdk.NewCoins(depositA, depositB) poolID := types.PoolIDFromCoins(deposit) expectedShares := sdkmath.NewInt(22360679) swapHooks.On("AfterPoolDepositCreated", suite.Ctx, poolID, depositor.GetAddress(), expectedShares).Run(func(args mock.Arguments) { _, found := suite.Keeper.GetDepositorShares(suite.Ctx, depositor.GetAddress(), poolID) suite.Require().True(found, "expected after hook to be called after shares are updated") }) err := suite.Keeper.Deposit(suite.Ctx, depositor.GetAddress(), depositA, depositB, sdk.MustNewDecFromStr("0.0015")) suite.Require().NoError(err) swapHooks.On("BeforePoolDepositModified", suite.Ctx, poolID, depositor.GetAddress(), expectedShares).Run(func(args mock.Arguments) { shareRecord, found := suite.Keeper.GetDepositorShares(suite.Ctx, depositor.GetAddress(), poolID) suite.Require().True(found, "expected share record to exist") suite.Equal(expectedShares, shareRecord.SharesOwned, "expected hook to be called before shares are updated") }) err = suite.Keeper.Deposit(suite.Ctx, depositor.GetAddress(), depositA, depositB, sdk.MustNewDecFromStr("0.0015")) suite.Require().NoError(err) existingShareRecord, found := suite.Keeper.GetDepositorShares(suite.Ctx, depositor.GetAddress(), types.PoolIDFromCoins(deposit)) suite.Require().True(found) swapHooks.On("BeforePoolDepositModified", suite.Ctx, poolID, depositor.GetAddress(), existingShareRecord.SharesOwned).Run(func(args mock.Arguments) { shareRecord, found := suite.Keeper.GetDepositorShares(suite.Ctx, depositor.GetAddress(), poolID) suite.Require().True(found, "expected share record to exist") suite.Equal(existingShareRecord.SharesOwned, shareRecord.SharesOwned, "expected hook to be called before shares are updated") }) err = suite.Keeper.Withdraw(suite.Ctx, depositor.GetAddress(), existingShareRecord.SharesOwned.Quo(sdkmath.NewInt(2)), sdk.NewCoin("ukava", sdkmath.NewInt(1)), sdk.NewCoin("usdx", sdkmath.NewInt(1))) suite.Require().NoError(err) }