mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 16:25:21 +00:00
Hook Regression Test + Deposit Refactor (#967)
* add regression test for hooks; refactor deposit to commit pool and shares in one place with commit after validation checks; panic on pool and share set methods if the record is invalid * use correct hook ordering; add regression test for order - fails if hooks are not called before or after share record set
This commit is contained in:
parent
da5a852a6c
commit
911f9f59d6
@ -44,14 +44,15 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coinA sdk.Coi
|
|||||||
poolRecord, found := k.GetPool(ctx, poolID)
|
poolRecord, found := k.GetPool(ctx, poolID)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
pool *types.DenominatedPool
|
||||||
depositAmount sdk.Coins
|
depositAmount sdk.Coins
|
||||||
shares sdk.Int
|
shares sdk.Int
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if found {
|
if found {
|
||||||
depositAmount, shares, err = k.addLiquidityToPool(ctx, poolRecord, depositor, desiredAmount)
|
pool, depositAmount, shares, err = k.addLiquidityToPool(ctx, poolRecord, depositor, desiredAmount)
|
||||||
} else {
|
} else {
|
||||||
depositAmount, shares, err = k.initializePool(ctx, poolID, depositor, desiredAmount)
|
pool, depositAmount, shares, err = k.initializePool(ctx, poolID, depositor, desiredAmount)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -75,6 +76,15 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coinA sdk.Coi
|
|||||||
return sdkerrors.Wrapf(types.ErrSlippageExceeded, "slippage %s > limit %s", slippage, slippageLimit)
|
return sdkerrors.Wrapf(types.ErrSlippageExceeded, "slippage %s > limit %s", slippage, slippageLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
k.updatePool(ctx, poolID, pool)
|
||||||
|
if shareRecord, hasExistingShares := k.GetDepositorShares(ctx, depositor, poolID); hasExistingShares {
|
||||||
|
k.BeforePoolDepositModified(ctx, poolID, depositor, shareRecord.SharesOwned)
|
||||||
|
k.updateShares(ctx, depositor, poolID, shareRecord.SharesOwned.Add(shares))
|
||||||
|
} else {
|
||||||
|
k.updateShares(ctx, depositor, poolID, shares)
|
||||||
|
k.AfterPoolDepositCreated(ctx, poolID, depositor, shares)
|
||||||
|
}
|
||||||
|
|
||||||
err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, depositor, types.ModuleAccountName, depositAmount)
|
err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, depositor, types.ModuleAccountName, depositAmount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -103,52 +113,26 @@ func (k Keeper) depositAllowed(ctx sdk.Context, poolID string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k Keeper) initializePool(ctx sdk.Context, poolID string, depositor sdk.AccAddress, reserves sdk.Coins) (sdk.Coins, sdk.Int, error) {
|
func (k Keeper) initializePool(ctx sdk.Context, poolID string, depositor sdk.AccAddress, reserves sdk.Coins) (*types.DenominatedPool, sdk.Coins, sdk.Int, error) {
|
||||||
if allowed := k.depositAllowed(ctx, poolID); !allowed {
|
if allowed := k.depositAllowed(ctx, poolID); !allowed {
|
||||||
return sdk.Coins{}, sdk.ZeroInt(), sdkerrors.Wrap(types.ErrNotAllowed, fmt.Sprintf("can not create pool '%s'", poolID))
|
return nil, sdk.Coins{}, sdk.ZeroInt(), sdkerrors.Wrap(types.ErrNotAllowed, fmt.Sprintf("can not create pool '%s'", poolID))
|
||||||
}
|
}
|
||||||
|
|
||||||
pool, err := types.NewDenominatedPool(reserves)
|
pool, err := types.NewDenominatedPool(reserves)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sdk.Coins{}, sdk.ZeroInt(), err
|
return nil, sdk.Coins{}, sdk.ZeroInt(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
poolRecord := types.NewPoolRecordFromPool(pool)
|
return pool, pool.Reserves(), pool.TotalShares(), nil
|
||||||
shareRecord := types.NewShareRecord(depositor, poolRecord.PoolID, pool.TotalShares())
|
|
||||||
|
|
||||||
k.SetPool(ctx, poolRecord)
|
|
||||||
k.SetDepositorShares(ctx, shareRecord)
|
|
||||||
|
|
||||||
k.hooks.AfterPoolDepositCreated(ctx, poolRecord.PoolID, depositor, shareRecord.SharesOwned)
|
|
||||||
|
|
||||||
return pool.Reserves(), pool.TotalShares(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k Keeper) addLiquidityToPool(ctx sdk.Context, record types.PoolRecord, depositor sdk.AccAddress, desiredAmount sdk.Coins) (sdk.Coins, sdk.Int, error) {
|
func (k Keeper) addLiquidityToPool(ctx sdk.Context, record types.PoolRecord, depositor sdk.AccAddress, desiredAmount sdk.Coins) (*types.DenominatedPool, sdk.Coins, sdk.Int, error) {
|
||||||
pool, err := types.NewDenominatedPoolWithExistingShares(record.Reserves(), record.TotalShares)
|
pool, err := types.NewDenominatedPoolWithExistingShares(record.Reserves(), record.TotalShares)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sdk.Coins{}, sdk.ZeroInt(), err
|
return nil, sdk.Coins{}, sdk.ZeroInt(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
depositAmount, shares := pool.AddLiquidity(desiredAmount)
|
depositAmount, shares := pool.AddLiquidity(desiredAmount)
|
||||||
|
|
||||||
poolRecord := types.NewPoolRecordFromPool(pool)
|
return pool, depositAmount, shares, nil
|
||||||
|
|
||||||
shareRecord, sharesFound := k.GetDepositorShares(ctx, depositor, poolRecord.PoolID)
|
|
||||||
if sharesFound {
|
|
||||||
k.hooks.BeforePoolDepositModified(ctx, poolRecord.PoolID, depositor, shareRecord.SharesOwned)
|
|
||||||
|
|
||||||
shareRecord.SharesOwned = shareRecord.SharesOwned.Add(shares)
|
|
||||||
} else {
|
|
||||||
shareRecord = types.NewShareRecord(depositor, poolRecord.PoolID, shares)
|
|
||||||
}
|
|
||||||
|
|
||||||
k.SetPool(ctx, poolRecord)
|
|
||||||
k.SetDepositorShares(ctx, shareRecord)
|
|
||||||
|
|
||||||
if !sharesFound {
|
|
||||||
k.hooks.AfterPoolDepositCreated(ctx, poolRecord.PoolID, depositor, shareRecord.SharesOwned)
|
|
||||||
}
|
|
||||||
|
|
||||||
return depositAmount, shares, nil
|
|
||||||
}
|
}
|
||||||
|
197
x/swap/keeper/hooks_test.go
Normal file
197
x/swap/keeper/hooks_test.go
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
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"
|
||||||
|
"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, sdk.NewInt(1000e6)),
|
||||||
|
sdk.NewCoin(pool.TokenB, sdk.NewInt(1000e6)),
|
||||||
|
)
|
||||||
|
depositor_1 := suite.CreateAccount(balance)
|
||||||
|
|
||||||
|
depositA := sdk.NewCoin(pool.TokenA, sdk.NewInt(10e6))
|
||||||
|
depositB := sdk.NewCoin(pool.TokenB, sdk.NewInt(50e6))
|
||||||
|
deposit := sdk.NewCoins(depositA, depositB)
|
||||||
|
|
||||||
|
// expected initial shares - geometric mean
|
||||||
|
expectedShares := sdk.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", sdk.NewInt(5e6)), sdk.NewCoin("usdx", sdk.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", sdk.NewInt(10e6)), sdk.NewCoin("usdx", sdk.NewInt(50e6)), sdk.MustNewDecFromStr("0.0015"))
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
depositor_2 := suite.NewAccountFromAddr(
|
||||||
|
sdk.AccAddress("depositor 2"),
|
||||||
|
sdk.NewCoins(
|
||||||
|
sdk.NewCoin("ukava", sdk.NewInt(100e6)),
|
||||||
|
sdk.NewCoin("usdx", sdk.NewInt(100e6)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// first deposit deposit into pool creates the deposit and calls AfterPoolDepositCreated
|
||||||
|
expectedShares = sdk.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", sdk.NewInt(1e6)), sdk.NewCoin("usdx", sdk.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", sdk.NewInt(2e6)), sdk.NewCoin("usdx", sdk.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", sdk.NewInt(3e6)), sdk.NewCoin("usdx", sdk.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", sdk.NewInt(1)), sdk.NewCoin("usdx", sdk.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(sdk.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", sdk.NewInt(1)), sdk.NewCoin("usdx", sdk.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(sdk.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", sdk.NewInt(1)), sdk.NewCoin("usdx", sdk.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", sdk.NewInt(1)), sdk.NewCoin("usdx", sdk.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, sdk.NewInt(1000e6)),
|
||||||
|
sdk.NewCoin(pool.TokenB, sdk.NewInt(1000e6)),
|
||||||
|
)
|
||||||
|
depositor := suite.CreateAccount(balance)
|
||||||
|
|
||||||
|
depositA := sdk.NewCoin(pool.TokenA, sdk.NewInt(10e6))
|
||||||
|
depositB := sdk.NewCoin(pool.TokenB, sdk.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", sdk.NewInt(5e6)), sdk.NewCoin("usdx", sdk.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", sdk.NewInt(1)), sdk.NewCoin("usdx", sdk.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, sdk.NewInt(1000e6)),
|
||||||
|
sdk.NewCoin(pool.TokenB, sdk.NewInt(1000e6)),
|
||||||
|
)
|
||||||
|
depositor := suite.CreateAccount(balance)
|
||||||
|
|
||||||
|
depositA := sdk.NewCoin(pool.TokenA, sdk.NewInt(10e6))
|
||||||
|
depositB := sdk.NewCoin(pool.TokenB, sdk.NewInt(50e6))
|
||||||
|
deposit := sdk.NewCoins(depositA, depositB)
|
||||||
|
|
||||||
|
poolID := types.PoolIDFromCoins(deposit)
|
||||||
|
expectedShares := sdk.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(sdk.NewInt(2)), sdk.NewCoin("ukava", sdk.NewInt(1)), sdk.NewCoin("usdx", sdk.NewInt(1)))
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
}
|
19
x/swap/keeper/invariant_test.go
Normal file
19
x/swap/keeper/invariant_test.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
//"github.com/kava-labs/kava/x/swap"
|
||||||
|
"github.com/kava-labs/kava/x/swap/testutil"
|
||||||
|
//"github.com/kava-labs/kava/x/swap/types"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
//sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type invariantTestSuite struct {
|
||||||
|
testutil.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenesisTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(invariantTestSuite))
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
package keeper
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/swap/types"
|
"github.com/kava-labs/kava/x/swap/types"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
@ -49,6 +51,11 @@ func (k *Keeper) SetHooks(sh types.SwapHooks) *Keeper {
|
|||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearHooks clears the hooks on the keeper
|
||||||
|
func (k *Keeper) ClearHooks() {
|
||||||
|
k.hooks = nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetParams returns the params from the store
|
// GetParams returns the params from the store
|
||||||
func (k Keeper) GetParams(ctx sdk.Context) types.Params {
|
func (k Keeper) GetParams(ctx sdk.Context) types.Params {
|
||||||
var p types.Params
|
var p types.Params
|
||||||
@ -81,13 +88,22 @@ func (k Keeper) GetPool(ctx sdk.Context, poolID string) (types.PoolRecord, bool)
|
|||||||
return record, true
|
return record, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPool saves a pool record to the store
|
// SetPool_Raw saves a pool record to the store without any validation
|
||||||
func (k Keeper) SetPool(ctx sdk.Context, record types.PoolRecord) {
|
func (k Keeper) SetPool_Raw(ctx sdk.Context, record types.PoolRecord) {
|
||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PoolKeyPrefix)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.PoolKeyPrefix)
|
||||||
bz := k.cdc.MustMarshalBinaryLengthPrefixed(record)
|
bz := k.cdc.MustMarshalBinaryLengthPrefixed(record)
|
||||||
store.Set(types.PoolKey(record.PoolID), bz)
|
store.Set(types.PoolKey(record.PoolID), bz)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetPool saves a pool to the store and panics if the record is invalid
|
||||||
|
func (k Keeper) SetPool(ctx sdk.Context, record types.PoolRecord) {
|
||||||
|
if err := record.Validate(); err != nil {
|
||||||
|
panic(fmt.Sprintf("invalid pool record: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
k.SetPool_Raw(ctx, record)
|
||||||
|
}
|
||||||
|
|
||||||
// DeletePool deletes a pool record from the store
|
// DeletePool deletes a pool record from the store
|
||||||
func (k Keeper) DeletePool(ctx sdk.Context, poolID string) {
|
func (k Keeper) DeletePool(ctx sdk.Context, poolID string) {
|
||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PoolKeyPrefix)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.PoolKeyPrefix)
|
||||||
@ -138,13 +154,22 @@ func (k Keeper) GetDepositorShares(ctx sdk.Context, depositor sdk.AccAddress, po
|
|||||||
return record, true
|
return record, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDepositorShares saves a share record to the store
|
// SetDepositorShares_Raw saves a share record to the store without validation
|
||||||
func (k Keeper) SetDepositorShares(ctx sdk.Context, record types.ShareRecord) {
|
func (k Keeper) SetDepositorShares_Raw(ctx sdk.Context, record types.ShareRecord) {
|
||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.DepositorPoolSharesPrefix)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.DepositorPoolSharesPrefix)
|
||||||
bz := k.cdc.MustMarshalBinaryLengthPrefixed(record)
|
bz := k.cdc.MustMarshalBinaryLengthPrefixed(record)
|
||||||
store.Set(types.DepositorPoolSharesKey(record.Depositor, record.PoolID), bz)
|
store.Set(types.DepositorPoolSharesKey(record.Depositor, record.PoolID), bz)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDepositorShares saves a share record to the store and panics if the record is invalid
|
||||||
|
func (k Keeper) SetDepositorShares(ctx sdk.Context, record types.ShareRecord) {
|
||||||
|
if err := record.Validate(); err != nil {
|
||||||
|
panic(fmt.Sprintf("invalid share record: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
k.SetDepositorShares_Raw(ctx, record)
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteDepositorShares deletes a share record from the store
|
// DeleteDepositorShares deletes a share record from the store
|
||||||
func (k Keeper) DeleteDepositorShares(ctx sdk.Context, depositor sdk.AccAddress, poolID string) {
|
func (k Keeper) DeleteDepositorShares(ctx sdk.Context, depositor sdk.AccAddress, poolID string) {
|
||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.DepositorPoolSharesPrefix)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.DepositorPoolSharesPrefix)
|
||||||
@ -198,10 +223,29 @@ func (k Keeper) GetAllDepositorSharesByOwner(ctx sdk.Context, owner sdk.AccAddre
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetDepositorSharesAmount gets a depositor's shares in a pool from the store
|
// GetDepositorSharesAmount gets a depositor's shares in a pool from the store
|
||||||
func (k *Keeper) GetDepositorSharesAmount(ctx sdk.Context, depositor sdk.AccAddress, poolID string) (sdk.Int, bool) {
|
func (k Keeper) GetDepositorSharesAmount(ctx sdk.Context, depositor sdk.AccAddress, poolID string) (sdk.Int, bool) {
|
||||||
record, found := k.GetDepositorShares(ctx, depositor, poolID)
|
record, found := k.GetDepositorShares(ctx, depositor, poolID)
|
||||||
if !found {
|
if !found {
|
||||||
return sdk.Int{}, false
|
return sdk.Int{}, false
|
||||||
}
|
}
|
||||||
return record.SharesOwned, true
|
return record.SharesOwned, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updatePool updates a pool, deleting the pool record if the shares are zero
|
||||||
|
func (k Keeper) updatePool(ctx sdk.Context, poolID string, pool *types.DenominatedPool) {
|
||||||
|
if pool.TotalShares().IsZero() {
|
||||||
|
k.DeletePool(ctx, poolID)
|
||||||
|
} else {
|
||||||
|
k.SetPool(ctx, types.NewPoolRecordFromPool(pool))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateShares updates a depositor shares records for a pool, deleting the record if the new shares are zero
|
||||||
|
func (k Keeper) updateShares(ctx sdk.Context, owner sdk.AccAddress, poolID string, shares sdk.Int) {
|
||||||
|
if shares.IsZero() {
|
||||||
|
k.DeleteDepositorShares(ctx, owner, poolID)
|
||||||
|
} else {
|
||||||
|
shareRecord := types.NewShareRecord(owner, poolID, shares)
|
||||||
|
k.SetDepositorShares(ctx, shareRecord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/kava-labs/kava/x/swap/testutil"
|
"github.com/kava-labs/kava/x/swap/testutil"
|
||||||
"github.com/kava-labs/kava/x/swap/types"
|
"github.com/kava-labs/kava/x/swap/types"
|
||||||
|
"github.com/kava-labs/kava/x/swap/types/mocks"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -106,6 +107,17 @@ func (suite *keeperTestSuite) TestPool_Persistance() {
|
|||||||
suite.Equal(deletedPool, types.PoolRecord{})
|
suite.Equal(deletedPool, types.PoolRecord{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *keeperTestSuite) TestPool_PanicsWhenInvalid() {
|
||||||
|
invalidRecord := types.NewPoolRecord(
|
||||||
|
sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100e6)), sdk.NewCoin("usdx", sdk.NewInt(100e6))),
|
||||||
|
i(-1),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.Panics(func() {
|
||||||
|
suite.Keeper.SetPool(suite.Ctx, invalidRecord)
|
||||||
|
}, "expected set pool to panic with invalid record")
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *keeperTestSuite) TestShare_Persistance() {
|
func (suite *keeperTestSuite) TestShare_Persistance() {
|
||||||
poolID := "ukava/usdx"
|
poolID := "ukava/usdx"
|
||||||
depositor := sdk.AccAddress("testAddress1")
|
depositor := sdk.AccAddress("testAddress1")
|
||||||
@ -127,3 +139,47 @@ func (suite *keeperTestSuite) TestShare_Persistance() {
|
|||||||
suite.False(ok)
|
suite.False(ok)
|
||||||
suite.Equal(deletedShares, types.ShareRecord{})
|
suite.Equal(deletedShares, types.ShareRecord{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *keeperTestSuite) TestShare_PanicsWhenInvalid() {
|
||||||
|
depositor, err := sdk.AccAddressFromBech32("kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
invalidRecord := types.NewShareRecord(
|
||||||
|
depositor,
|
||||||
|
"hard/usdx",
|
||||||
|
i(-1),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.Panics(func() {
|
||||||
|
suite.Keeper.SetDepositorShares(suite.Ctx, invalidRecord)
|
||||||
|
}, "expected set depositor shares to panic with invalid record")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *keeperTestSuite) TestHooks() {
|
||||||
|
// ensure no hooks are set
|
||||||
|
suite.Keeper.ClearHooks()
|
||||||
|
|
||||||
|
// data
|
||||||
|
depositor, err := sdk.AccAddressFromBech32("kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// hooks can be called when not set
|
||||||
|
suite.Keeper.AfterPoolDepositCreated(suite.Ctx, "ukava/usdx", depositor, sdk.NewInt(1e6))
|
||||||
|
suite.Keeper.BeforePoolDepositModified(suite.Ctx, "ukava/usdx", depositor, sdk.NewInt(1e6))
|
||||||
|
|
||||||
|
// set hooks
|
||||||
|
swapHooks := &mocks.SwapHooks{}
|
||||||
|
suite.Keeper.SetHooks(swapHooks)
|
||||||
|
|
||||||
|
// test hook calls are correct
|
||||||
|
swapHooks.On("AfterPoolDepositCreated", suite.Ctx, "ukava/usdx", depositor, sdk.NewInt(1e6)).Once()
|
||||||
|
suite.Keeper.AfterPoolDepositCreated(suite.Ctx, "ukava/usdx", depositor, sdk.NewInt(1e6))
|
||||||
|
swapHooks.On("BeforePoolDepositModified", suite.Ctx, "ukava/usdx", depositor, sdk.NewInt(1e6)).Once()
|
||||||
|
suite.Keeper.BeforePoolDepositModified(suite.Ctx, "ukava/usdx", depositor, sdk.NewInt(1e6))
|
||||||
|
swapHooks.AssertExpectations(suite.T())
|
||||||
|
|
||||||
|
// test second set panics
|
||||||
|
suite.PanicsWithValue("cannot set swap hooks twice", func() {
|
||||||
|
suite.Keeper.SetHooks(swapHooks)
|
||||||
|
}, "expected hooks to panic on second set")
|
||||||
|
}
|
||||||
|
@ -268,7 +268,7 @@ func (suite *keeperTestSuite) TestSwapExactForTokens_PanicOnInvalidPool() {
|
|||||||
suite.Require().True(found, "expected pool record to exist")
|
suite.Require().True(found, "expected pool record to exist")
|
||||||
|
|
||||||
poolRecord.TotalShares = sdk.ZeroInt()
|
poolRecord.TotalShares = sdk.ZeroInt()
|
||||||
suite.Keeper.SetPool(suite.Ctx, poolRecord)
|
suite.Keeper.SetPool_Raw(suite.Ctx, poolRecord)
|
||||||
|
|
||||||
balance := sdk.NewCoins(
|
balance := sdk.NewCoins(
|
||||||
sdk.NewCoin("ukava", sdk.NewInt(10e6)),
|
sdk.NewCoin("ukava", sdk.NewInt(10e6)),
|
||||||
@ -579,7 +579,7 @@ func (suite *keeperTestSuite) TestSwapForExactTokens_PanicOnInvalidPool() {
|
|||||||
suite.Require().True(found, "expected pool record to exist")
|
suite.Require().True(found, "expected pool record to exist")
|
||||||
|
|
||||||
poolRecord.TotalShares = sdk.ZeroInt()
|
poolRecord.TotalShares = sdk.ZeroInt()
|
||||||
suite.Keeper.SetPool(suite.Ctx, poolRecord)
|
suite.Keeper.SetPool_Raw(suite.Ctx, poolRecord)
|
||||||
|
|
||||||
balance := sdk.NewCoins(
|
balance := sdk.NewCoins(
|
||||||
sdk.NewCoin("ukava", sdk.NewInt(10e6)),
|
sdk.NewCoin("ukava", sdk.NewInt(10e6)),
|
||||||
|
@ -52,8 +52,7 @@ func (k Keeper) Withdraw(ctx sdk.Context, owner sdk.AccAddress, shares sdk.Int,
|
|||||||
}
|
}
|
||||||
|
|
||||||
k.updatePool(ctx, poolID, pool)
|
k.updatePool(ctx, poolID, pool)
|
||||||
|
k.BeforePoolDepositModified(ctx, poolID, owner, shareRecord.SharesOwned)
|
||||||
k.hooks.BeforePoolDepositModified(ctx, poolID, owner, shareRecord.SharesOwned)
|
|
||||||
k.updateShares(ctx, owner, poolID, shareRecord.SharesOwned.Sub(shares))
|
k.updateShares(ctx, owner, poolID, shareRecord.SharesOwned.Sub(shares))
|
||||||
|
|
||||||
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, owner, withdrawnAmount)
|
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, owner, withdrawnAmount)
|
||||||
@ -73,20 +72,3 @@ func (k Keeper) Withdraw(ctx sdk.Context, owner sdk.AccAddress, shares sdk.Int,
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k Keeper) updatePool(ctx sdk.Context, poolID string, pool *types.DenominatedPool) {
|
|
||||||
if pool.TotalShares().IsZero() {
|
|
||||||
k.DeletePool(ctx, poolID)
|
|
||||||
} else {
|
|
||||||
k.SetPool(ctx, types.NewPoolRecordFromPool(pool))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k Keeper) updateShares(ctx sdk.Context, owner sdk.AccAddress, poolID string, shares sdk.Int) {
|
|
||||||
if shares.IsZero() {
|
|
||||||
k.DeleteDepositorShares(ctx, owner, poolID)
|
|
||||||
} else {
|
|
||||||
shareRecord := types.NewShareRecord(owner, poolID, shares)
|
|
||||||
k.SetDepositorShares(ctx, shareRecord)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -196,7 +196,7 @@ func (suite *keeperTestSuite) TestWithdraw_PanicOnInvalidPool() {
|
|||||||
suite.Require().True(found, "expected pool record to exist")
|
suite.Require().True(found, "expected pool record to exist")
|
||||||
|
|
||||||
poolRecord.TotalShares = sdk.ZeroInt()
|
poolRecord.TotalShares = sdk.ZeroInt()
|
||||||
suite.Keeper.SetPool(suite.Ctx, poolRecord)
|
suite.Keeper.SetPool_Raw(suite.Ctx, poolRecord)
|
||||||
|
|
||||||
suite.PanicsWithValue("invalid pool ukava/usdx: invalid pool: total shares must be greater than zero", func() {
|
suite.PanicsWithValue("invalid pool ukava/usdx: invalid pool: total shares must be greater than zero", func() {
|
||||||
_ = suite.Keeper.Withdraw(suite.Ctx, owner.GetAddress(), totalShares, reserves[0], reserves[1])
|
_ = suite.Keeper.Withdraw(suite.Ctx, owner.GetAddress(), totalShares, reserves[0], reserves[1])
|
||||||
|
24
x/swap/types/mocks/swap_hooks.go
Normal file
24
x/swap/types/mocks/swap_hooks.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Code generated by mockery 2.7.4. DO NOT EDIT.
|
||||||
|
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
|
types "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SwapHooks is an autogenerated mock type for the SwapHooks type
|
||||||
|
type SwapHooks struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// AfterPoolDepositCreated provides a mock function with given fields: ctx, poolID, depositor, sharedOwned
|
||||||
|
func (_m *SwapHooks) AfterPoolDepositCreated(ctx types.Context, poolID string, depositor types.AccAddress, sharedOwned types.Int) {
|
||||||
|
_m.Called(ctx, poolID, depositor, sharedOwned)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeforePoolDepositModified provides a mock function with given fields: ctx, poolID, depositor, sharedOwned
|
||||||
|
func (_m *SwapHooks) BeforePoolDepositModified(ctx types.Context, poolID string, depositor types.AccAddress, sharedOwned types.Int) {
|
||||||
|
_m.Called(ctx, poolID, depositor, sharedOwned)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user