From 25dbb2dc2de0811cb42841b4520c9ee5b8352b72 Mon Sep 17 00:00:00 2001 From: Hanjun Kim Date: Thu, 27 May 2021 21:23:00 +0900 Subject: [PATCH] treat pool with zero pool coin supply as depleted also change Mul/QuoTruncate to Mul/Quo. fixes #364 --- x/liquidity/keeper/liquidity_pool.go | 82 ++++++------ x/liquidity/keeper/liquidity_pool_test.go | 144 +++++++++++++++++----- x/liquidity/keeper/swap.go | 4 +- x/liquidity/keeper/swap_test.go | 23 ++++ 4 files changed, 183 insertions(+), 70 deletions(-) diff --git a/x/liquidity/keeper/liquidity_pool.go b/x/liquidity/keeper/liquidity_pool.go index 1f54c14d1..3345d63dd 100644 --- a/x/liquidity/keeper/liquidity_pool.go +++ b/x/liquidity/keeper/liquidity_pool.go @@ -77,32 +77,32 @@ func (k Keeper) CreatePool(ctx sdk.Context, msg *types.MsgCreatePool) (types.Poo poolName := types.PoolName(reserveCoinDenoms, msg.PoolTypeId) reserveAcc := types.GetPoolReserveAcc(poolName) - poolCreator := msg.GetPoolCreator() - accPoolCreator := k.accountKeeper.GetAccount(ctx, poolCreator) - poolCreatorBalances := k.bankKeeper.GetAllBalances(ctx, accPoolCreator.GetAddress()) - - if !poolCreatorBalances.IsAllGTE(msg.DepositCoins) { - return types.Pool{}, types.ErrInsufficientBalance + poolCoinDenom := types.GetPoolCoinDenom(poolName) + pool := types.Pool{ + //Id: will set on SetPoolAtomic + TypeId: msg.PoolTypeId, + ReserveCoinDenoms: reserveCoinDenoms, + ReserveAccountAddress: reserveAcc.String(), + PoolCoinDenom: poolCoinDenom, } + reserveCoins := k.GetReserveCoins(ctx, pool) + + poolCreator := msg.GetPoolCreator() + poolCreatorBalances := k.bankKeeper.GetAllBalances(ctx, poolCreator) + for _, coin := range msg.DepositCoins { - if coin.Amount.LT(params.MinInitDepositAmount) { + if coin.Amount.Add(reserveCoins.AmountOf(coin.Denom)).LT(params.MinInitDepositAmount) { return types.Pool{}, types.ErrLessThanMinInitDeposit } } - if !poolCreatorBalances.IsAllGTE(params.PoolCreationFee.Add(msg.DepositCoins...)) { - return types.Pool{}, types.ErrInsufficientPoolCreationFee + if !poolCreatorBalances.IsAllGTE(msg.DepositCoins) { + return types.Pool{}, types.ErrInsufficientBalance } - PoolCoinDenom := types.GetPoolCoinDenom(poolName) - - pool := types.Pool{ - //Id: will set on SetPoolAtomic - TypeId: msg.PoolTypeId, - ReserveCoinDenoms: reserveCoinDenoms, - ReserveAccountAddress: reserveAcc.String(), - PoolCoinDenom: PoolCoinDenom, + if !poolCreatorBalances.IsAllGTE(msg.DepositCoins.Add(params.PoolCreationFee...)) { + return types.Pool{}, types.ErrInsufficientPoolCreationFee } batchEscrowAcc := k.accountKeeper.GetModuleAddress(types.ModuleName) @@ -136,8 +136,8 @@ func (k Keeper) CreatePool(ctx sdk.Context, msg *types.MsgCreatePool) (types.Poo k.SetPoolBatch(ctx, batch) - reserveCoins := k.GetReserveCoins(ctx, pool) - lastReserveRatio := sdk.NewDecFromInt(reserveCoins[0].Amount).QuoTruncate(sdk.NewDecFromInt(reserveCoins[1].Amount)) + reserveCoins = k.GetReserveCoins(ctx, pool) + lastReserveRatio := sdk.NewDecFromInt(reserveCoins[0].Amount).Quo(sdk.NewDecFromInt(reserveCoins[1].Amount)) logger := k.Logger(ctx) logger.Debug("createPool", msg, "pool", pool, "reserveCoins", reserveCoins, "lastReserveRatio", lastReserveRatio) return pool, nil @@ -172,10 +172,10 @@ func (k Keeper) DepositLiquidityPool(ctx sdk.Context, msg types.DepositMsgState, reserveCoins := k.GetReserveCoins(ctx, pool) - // reinitialize pool in case of reserve coins has run out - if reserveCoins.IsZero() { + // reinitialize pool if the pool is depleted + if k.IsDepletedPool(ctx, pool) { for _, depositCoin := range msg.Msg.DepositCoins { - if depositCoin.Amount.LT(params.MinInitDepositAmount) { + if depositCoin.Amount.Add(reserveCoins.AmountOf(depositCoin.Denom)).LT(params.MinInitDepositAmount) { return types.ErrLessThanMinInitDeposit } } @@ -202,10 +202,10 @@ func (k Keeper) DepositLiquidityPool(ctx sdk.Context, msg types.DepositMsgState, msg.ToBeDeleted = true k.SetPoolBatchDepositMsgState(ctx, msg.Msg.PoolId, msg) - reserveCoins := k.GetReserveCoins(ctx, pool) + reserveCoins = k.GetReserveCoins(ctx, pool) lastReserveCoinA := sdk.NewDecFromInt(reserveCoins[0].Amount) lastReserveCoinB := sdk.NewDecFromInt(reserveCoins[1].Amount) - lastReserveRatio := lastReserveCoinA.QuoTruncate(lastReserveCoinB) + lastReserveRatio := lastReserveCoinA.Quo(lastReserveCoinB) ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeDepositToPool, @@ -236,13 +236,13 @@ func (k Keeper) DepositLiquidityPool(ctx sdk.Context, msg types.DepositMsgState, // Decimal Error, divide the Int coin amount by the Decimal Rate and erase the decimal point to deposit a lower value lastReserveCoinA := sdk.NewDecFromInt(reserveCoins[0].Amount) lastReserveCoinB := sdk.NewDecFromInt(reserveCoins[1].Amount) - lastReserveRatio := lastReserveCoinA.QuoTruncate(lastReserveCoinB) + lastReserveRatio := lastReserveCoinA.Quo(lastReserveCoinB) depositCoinA := depositCoins[0] depositCoinB := depositCoins[1] depositCoinAmountA := depositCoinA.Amount depositCoinAmountB := depositCoinB.Amount - depositableCoinAmountA := depositCoinB.Amount.ToDec().MulTruncate(lastReserveRatio).TruncateInt() + depositableCoinAmountA := depositCoinB.Amount.ToDec().Mul(lastReserveRatio).TruncateInt() acceptedCoins := sdk.NewCoins() refundedCoins := sdk.NewCoins() @@ -251,7 +251,7 @@ func (k Keeper) DepositLiquidityPool(ctx sdk.Context, msg types.DepositMsgState, // handle when depositing coin A amount is less than, greater than or equal to depositable amount if depositCoinA.Amount.LT(depositableCoinAmountA) { - depositCoinAmountB = depositCoinA.Amount.ToDec().QuoTruncate(lastReserveRatio).TruncateInt() + depositCoinAmountB = depositCoinA.Amount.ToDec().Quo(lastReserveRatio).TruncateInt() acceptedCoins = sdk.NewCoins(depositCoinA, sdk.NewCoin(depositCoinB.Denom, depositCoinAmountB)) inputs = append(inputs, banktypes.NewInput(batchEscrowAcc, acceptedCoins)) @@ -265,7 +265,7 @@ func (k Keeper) DepositLiquidityPool(ctx sdk.Context, msg types.DepositMsgState, outputs = append(outputs, banktypes.NewOutput(depositor, refundedCoins)) } } else if depositCoinA.Amount.GT(depositableCoinAmountA) { - depositCoinAmountA = depositCoinB.Amount.ToDec().MulTruncate(lastReserveRatio).TruncateInt() + depositCoinAmountA = depositCoinB.Amount.ToDec().Mul(lastReserveRatio).TruncateInt() acceptedCoins = sdk.NewCoins(depositCoinB, sdk.NewCoin(depositCoinA.Denom, depositCoinAmountA)) inputs = append(inputs, banktypes.NewInput(batchEscrowAcc, acceptedCoins)) @@ -313,7 +313,7 @@ func (k Keeper) DepositLiquidityPool(ctx sdk.Context, msg types.DepositMsgState, afterReserveCoins := k.GetReserveCoins(ctx, pool) afterReserveCoinA := sdk.NewDecFromInt(afterReserveCoins[0].Amount) afterReserveCoinB := sdk.NewDecFromInt(afterReserveCoins[1].Amount) - afterReserveRatio := afterReserveCoinA.QuoTruncate(afterReserveCoinB) + afterReserveRatio := afterReserveCoinA.Quo(afterReserveCoinB) poolCoinTotalSupplyDec := sdk.NewDecFromInt(poolCoinTotalSupply) mintPoolCoinDec := sdk.NewDecFromInt(mintPoolCoin.Amount) depositCoinADec := sdk.NewDecFromInt(depositCoinA.Amount) @@ -345,7 +345,7 @@ func (k Keeper) DepositLiquidityPool(ctx sdk.Context, msg types.DepositMsgState, ) reserveCoins = k.GetReserveCoins(ctx, pool) - lastReserveRatio = sdk.NewDecFromInt(reserveCoins[0].Amount).QuoTruncate(sdk.NewDecFromInt(reserveCoins[1].Amount)) + lastReserveRatio = sdk.NewDecFromInt(reserveCoins[0].Amount).Quo(sdk.NewDecFromInt(reserveCoins[1].Amount)) logger := k.Logger(ctx) logger.Debug("deposit", msg, "pool", pool, "inputs", inputs, "outputs", outputs, "reserveCoins", reserveCoins, "lastReserveRatio", lastReserveRatio) @@ -393,7 +393,7 @@ func (k Keeper) WithdrawLiquidityPool(ctx sdk.Context, msg types.WithdrawMsgStat // Calculate withdraw amount of respective reserve coin considering fees and pool coin's totally supply for _, reserveCoin := range reserveCoins { // WithdrawAmount = ReserveAmount * PoolCoinAmount * WithdrawFeeProportion / TotalSupply - withdrawAmt := reserveCoin.Amount.Mul(msg.Msg.PoolCoin.Amount).ToDec().MulTruncate(withdrawProportion).TruncateInt().Quo(poolCoinTotalSupply) + withdrawAmt := reserveCoin.Amount.Mul(msg.Msg.PoolCoin.Amount).ToDec().Mul(withdrawProportion).TruncateInt().Quo(poolCoinTotalSupply) withdrawCoins = append(withdrawCoins, sdk.NewCoin(reserveCoin.Denom, withdrawAmt)) } } @@ -465,7 +465,7 @@ func (k Keeper) WithdrawLiquidityPool(ctx sdk.Context, msg types.WithdrawMsgStat if reserveCoins.IsZero() { lastReserveRatio = sdk.ZeroDec() } else { - lastReserveRatio = sdk.NewDecFromInt(reserveCoins[0].Amount).QuoTruncate(sdk.NewDecFromInt(reserveCoins[1].Amount)) + lastReserveRatio = sdk.NewDecFromInt(reserveCoins[0].Amount).Quo(sdk.NewDecFromInt(reserveCoins[1].Amount)) } logger := k.Logger(ctx) @@ -481,6 +481,11 @@ func (k Keeper) GetPoolCoinTotalSupply(ctx sdk.Context, pool types.Pool) sdk.Int return total.AmountOf(pool.PoolCoinDenom) } +// IsDepletedPool returns true if the pool is depleted. +func (k Keeper) IsDepletedPool(ctx sdk.Context, pool types.Pool) bool { + return !k.GetPoolCoinTotalSupply(ctx, pool).IsPositive() +} + // GetPoolCoinTotal returns total supply of pool coin of the pool in form of sdk.Coin func (k Keeper) GetPoolCoinTotal(ctx sdk.Context, pool types.Pool) sdk.Coin { return sdk.NewCoin(pool.PoolCoinDenom, k.GetPoolCoinTotalSupply(ctx, pool)) @@ -738,8 +743,7 @@ func (k Keeper) ValidateMsgWithdrawLiquidityPool(ctx sdk.Context, msg types.MsgW } poolCoinTotalSupply := k.GetPoolCoinTotalSupply(ctx, pool) - - if !poolCoinTotalSupply.IsPositive() || !k.GetReserveCoins(ctx, pool).IsAllPositive() { + if !poolCoinTotalSupply.IsPositive() { return types.ErrDepletedPool } @@ -765,17 +769,17 @@ func (k Keeper) ValidateMsgSwapWithinBatch(ctx sdk.Context, msg types.MsgSwapWit return types.ErrNotMatchedReserveCoin } + if k.IsDepletedPool(ctx, pool) { + return types.ErrDepletedPool + } + params := k.GetParams(ctx) // can not exceed max order ratio of reserve coins that can be ordered at a order reserveCoinAmt := k.GetReserveCoins(ctx, pool).AmountOf(msg.OfferCoin.Denom) - if !reserveCoinAmt.IsPositive() { - return types.ErrDepletedPool - } - // Decimal Error, Multiply the Int coin amount by the Decimal Rate and erase the decimal point to order a lower value - maximumOrderableAmt := reserveCoinAmt.ToDec().MulTruncate(params.MaxOrderAmountRatio).TruncateInt() + maximumOrderableAmt := reserveCoinAmt.ToDec().Mul(params.MaxOrderAmountRatio).TruncateInt() if msg.OfferCoin.Amount.GT(maximumOrderableAmt) { return types.ErrExceededMaxOrderable } diff --git a/x/liquidity/keeper/liquidity_pool_test.go b/x/liquidity/keeper/liquidity_pool_test.go index 30d731ba4..df3ec00df 100644 --- a/x/liquidity/keeper/liquidity_pool_test.go +++ b/x/liquidity/keeper/liquidity_pool_test.go @@ -9,7 +9,7 @@ import ( distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/stretchr/testify/require" - lapp "github.com/tendermint/liquidity/app" + "github.com/tendermint/liquidity/app" "github.com/tendermint/liquidity/x/liquidity" "github.com/tendermint/liquidity/x/liquidity/types" ) @@ -36,14 +36,14 @@ func TestCreatePool(t *testing.T) { params := simapp.LiquidityKeeper.GetParams(ctx) poolTypeId := types.DefaultPoolTypeId - addrs := lapp.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) + addrs := app.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) denomA := "uETH" denomB := "uUSD" denomA, denomB = types.AlphabeticalDenomPair(denomA, denomB) deposit := sdk.NewCoins(sdk.NewCoin(denomA, sdk.NewInt(100*1000000)), sdk.NewCoin(denomB, sdk.NewInt(2000*1000000))) - lapp.SaveAccount(simapp, ctx, addrs[0], deposit) + app.SaveAccount(simapp, ctx, addrs[0], deposit) depositA := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomA) depositB := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomB) @@ -80,14 +80,14 @@ func TestPoolCreationFee(t *testing.T) { params := simapp.LiquidityKeeper.GetParams(ctx) poolTypeId := types.DefaultPoolTypeId - addrs := lapp.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) + addrs := app.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) denomA := "uETH" denomB := "uUSD" denomA, denomB = types.AlphabeticalDenomPair(denomA, denomB) deposit := sdk.NewCoins(sdk.NewCoin(denomA, sdk.NewInt(100*1000000)), sdk.NewCoin(denomB, sdk.NewInt(2000*1000000))) - lapp.SaveAccount(simapp, ctx, addrs[0], deposit) + app.SaveAccount(simapp, ctx, addrs[0], deposit) depositA := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomA) depositB := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomB) @@ -124,15 +124,15 @@ func TestDepositLiquidityPool(t *testing.T) { params := simapp.LiquidityKeeper.GetParams(ctx) poolTypeId := types.DefaultPoolTypeId - addrs := lapp.AddTestAddrs(simapp, ctx, 4, params.PoolCreationFee) + addrs := app.AddTestAddrs(simapp, ctx, 4, params.PoolCreationFee) denomA := "uETH" denomB := "uUSD" denomA, denomB = types.AlphabeticalDenomPair(denomA, denomB) deposit := sdk.NewCoins(sdk.NewCoin(denomA, sdk.NewInt(100*1000000)), sdk.NewCoin(denomB, sdk.NewInt(2000*1000000))) - lapp.SaveAccount(simapp, ctx, addrs[0], deposit) - lapp.SaveAccount(simapp, ctx, addrs[1], deposit) + app.SaveAccount(simapp, ctx, addrs[0], deposit) + app.SaveAccount(simapp, ctx, addrs[1], deposit) depositA := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomA) depositB := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomB) @@ -180,14 +180,14 @@ func TestReserveCoinLimit(t *testing.T) { simapp.LiquidityKeeper.SetParams(ctx, params) poolTypeId := types.DefaultPoolTypeId - addrs := lapp.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) + addrs := app.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) denomA := "uETH" denomB := "uUSD" denomA, denomB = types.AlphabeticalDenomPair(denomA, denomB) deposit := sdk.NewCoins(sdk.NewCoin(denomA, params.MaxReserveCoinAmount), sdk.NewCoin(denomB, sdk.NewInt(1000000))) - lapp.SaveAccount(simapp, ctx, addrs[0], deposit) + app.SaveAccount(simapp, ctx, addrs[0], deposit) depositA := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomA) depositB := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomB) depositBalance := sdk.NewCoins(depositA, depositB) @@ -209,7 +209,7 @@ func TestReserveCoinLimit(t *testing.T) { pool := pools[0] deposit = sdk.NewCoins(sdk.NewCoin(denomA, sdk.NewInt(1000000)), sdk.NewCoin(denomB, sdk.NewInt(1000000))) - lapp.SaveAccount(simapp, ctx, addrs[1], deposit) + app.SaveAccount(simapp, ctx, addrs[1], deposit) depositMsg := types.NewMsgDepositWithinBatch(addrs[1], pool.Id, deposit) _, err = simapp.LiquidityKeeper.DepositLiquidityPoolToBatch(ctx, depositMsg) require.Equal(t, types.ErrExceededReserveCoinLimit, err) @@ -231,7 +231,7 @@ func TestReserveCoinLimit(t *testing.T) { ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) simapp.LiquidityKeeper.DeleteAndInitPoolBatch(ctx) - lapp.SaveAccount(simapp, ctx, addrs[1], deposit) + app.SaveAccount(simapp, ctx, addrs[1], deposit) depositMsg = types.NewMsgDepositWithinBatch(addrs[1], pool.Id, deposit) _, err = simapp.LiquidityKeeper.DepositLiquidityPoolToBatch(ctx, depositMsg) require.NoError(t, err) @@ -254,14 +254,14 @@ func TestWithdrawLiquidityPool(t *testing.T) { params := simapp.LiquidityKeeper.GetParams(ctx) poolTypeId := types.DefaultPoolTypeId - addrs := lapp.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) + addrs := app.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) denomA := "uETH" denomB := "uUSD" denomA, denomB = types.AlphabeticalDenomPair(denomA, denomB) deposit := sdk.NewCoins(sdk.NewCoin(denomA, sdk.NewInt(100*1000000)), sdk.NewCoin(denomB, sdk.NewInt(2000*1000000))) - lapp.SaveAccount(simapp, ctx, addrs[0], deposit) + app.SaveAccount(simapp, ctx, addrs[0], deposit) depositA := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomA) depositB := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomB) @@ -342,14 +342,14 @@ func TestReinitializePool(t *testing.T) { simapp.LiquidityKeeper.SetParams(ctx, params) poolTypeId := types.DefaultPoolTypeId - addrs := lapp.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) + addrs := app.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) denomA := "uETH" denomB := "uUSD" denomA, denomB = types.AlphabeticalDenomPair(denomA, denomB) deposit := sdk.NewCoins(sdk.NewCoin(denomA, sdk.NewInt(100*1000000)), sdk.NewCoin(denomB, sdk.NewInt(100*1000000))) - lapp.SaveAccount(simapp, ctx, addrs[0], deposit) + app.SaveAccount(simapp, ctx, addrs[0], deposit) depositA := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomA) depositB := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomB) @@ -427,7 +427,7 @@ func TestReserveAccManipulation(t *testing.T) { params := simapp.LiquidityKeeper.GetParams(ctx) poolTypeId := types.DefaultPoolTypeId - addrs := lapp.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) + addrs := app.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) denomA := "uETH" denomB := "uUSD" @@ -436,9 +436,9 @@ func TestReserveAccManipulation(t *testing.T) { deposit := sdk.NewCoins(sdk.NewCoin(denomA, sdk.NewInt(100*1000000)), sdk.NewCoin(denomB, sdk.NewInt(2000*1000000))) // depositor, withdrawer - lapp.SaveAccount(simapp, ctx, addrs[0], deposit) + app.SaveAccount(simapp, ctx, addrs[0], deposit) // reserveAccount manipulator - lapp.SaveAccount(simapp, ctx, addrs[1], deposit) + app.SaveAccount(simapp, ctx, addrs[1], deposit) depositA := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomA) depositB := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomB) @@ -512,14 +512,14 @@ func TestGetLiquidityPoolMetadata(t *testing.T) { params := simapp.LiquidityKeeper.GetParams(ctx) poolTypeId := types.DefaultPoolTypeId - addrs := lapp.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) + addrs := app.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) denomA := "uETH" denomB := "uUSD" denomA, denomB = types.AlphabeticalDenomPair(denomA, denomB) deposit := sdk.NewCoins(sdk.NewCoin(denomA, sdk.NewInt(100*1000000)), sdk.NewCoin(denomB, sdk.NewInt(2000*1000000))) - lapp.SaveAccount(simapp, ctx, addrs[0], deposit) + app.SaveAccount(simapp, ctx, addrs[0], deposit) depositA := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomA) depositB := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomB) @@ -564,14 +564,14 @@ func TestIsPoolCoinDenom(t *testing.T) { params := simapp.LiquidityKeeper.GetParams(ctx) poolTypeId := types.DefaultPoolTypeId - addrs := lapp.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) + addrs := app.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) denomA := "denomA" denomB := "denomB" denomA, denomB = types.AlphabeticalDenomPair(denomA, denomB) deposit := sdk.NewCoins(sdk.NewCoin(denomA, sdk.NewInt(100*1000000)), sdk.NewCoin(denomB, sdk.NewInt(2000*1000000))) - lapp.SaveAccount(simapp, ctx, addrs[0], deposit) + app.SaveAccount(simapp, ctx, addrs[0], deposit) depositA := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomA) depositB := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomB) @@ -598,14 +598,14 @@ func TestGetPoolByReserveAccIndex(t *testing.T) { params := simapp.LiquidityKeeper.GetParams(ctx) poolTypeId := types.DefaultPoolTypeId - addrs := lapp.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) + addrs := app.AddTestAddrs(simapp, ctx, 3, params.PoolCreationFee) denomA := "uETH" denomB := "uUSD" denomA, denomB = types.AlphabeticalDenomPair(denomA, denomB) deposit := sdk.NewCoins(sdk.NewCoin(denomA, sdk.NewInt(100*1000000)), sdk.NewCoin(denomB, sdk.NewInt(2000*1000000))) - lapp.SaveAccount(simapp, ctx, addrs[0], deposit) + app.SaveAccount(simapp, ctx, addrs[0], deposit) depositA := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomA) depositB := simapp.BankKeeper.GetBalance(ctx, addrs[0], denomB) @@ -642,11 +642,11 @@ func TestDepositWithdrawEdgecase(t *testing.T) { simapp, ctx := createTestInput() params := simapp.LiquidityKeeper.GetParams(ctx) - X := params.MinInitDepositAmount.Add(lapp.GetRandRange(r, 0, 1_000_000)) - Y := params.MinInitDepositAmount.Add(lapp.GetRandRange(r, 0, 1_000_000)) + X := params.MinInitDepositAmount.Add(app.GetRandRange(r, 0, 1_000_000)) + Y := params.MinInitDepositAmount.Add(app.GetRandRange(r, 0, 1_000_000)) creatorCoins := sdk.NewCoins(sdk.NewCoin(DenomX, X), sdk.NewCoin(DenomY, Y)) - creatorAddr := lapp.AddRandomTestAddr(simapp, ctx, creatorCoins.Add(params.PoolCreationFee...)) + creatorAddr := app.AddRandomTestAddr(simapp, ctx, creatorCoins.Add(params.PoolCreationFee...)) pool, err := simapp.LiquidityKeeper.CreatePool(ctx, types.NewMsgCreatePool(creatorAddr, types.DefaultPoolTypeId, creatorCoins)) require.NoError(t, err) @@ -698,7 +698,7 @@ func TestWithdrawEdgecase(t *testing.T) { X, Y := sdk.NewInt(1_000_000), sdk.NewInt(10_000_000) depositCoins := sdk.NewCoins(sdk.NewCoin(DenomX, X), sdk.NewCoin(DenomY, Y)) - creatorAddr := lapp.AddRandomTestAddr(simapp, ctx, depositCoins.Add(params.PoolCreationFee...)) + creatorAddr := app.AddRandomTestAddr(simapp, ctx, depositCoins.Add(params.PoolCreationFee...)) pool, err := simapp.LiquidityKeeper.CreatePool(ctx, types.NewMsgCreatePool(creatorAddr, types.DefaultPoolTypeId, depositCoins)) require.NoError(t, err) @@ -782,3 +782,89 @@ func TestGetReserveCoins(t *testing.T) { require.True(t, reserveCoins.AmountOf(DenomX).IsZero()) require.True(t, reserveCoins.AmountOf(DenomY).IsZero()) } + +func TestDepositToDepletedPool(t *testing.T) { + simapp, ctx, pool, creatorAddr, err := createTestPool(sdk.NewInt64Coin(DenomX, 1000000), sdk.NewInt64Coin(DenomY, 1000000)) + require.NoError(t, err) + params := simapp.LiquidityKeeper.GetParams(ctx) + + liquidity.BeginBlocker(ctx, simapp.LiquidityKeeper) + pc := simapp.BankKeeper.GetBalance(ctx, creatorAddr, pool.PoolCoinDenom) + _, err = simapp.LiquidityKeeper.WithdrawLiquidityPoolToBatch(ctx, types.NewMsgWithdrawWithinBatch(creatorAddr, pool.Id, pc)) + require.NoError(t, err) + liquidity.EndBlocker(ctx, simapp.LiquidityKeeper) + + reserveCoins := simapp.LiquidityKeeper.GetReserveCoins(ctx, pool) + require.True(t, reserveCoins.AmountOf(DenomX).IsZero()) + require.True(t, reserveCoins.AmountOf(DenomY).IsZero()) + + require.True(t, simapp.LiquidityKeeper.IsDepletedPool(ctx, pool)) + + err = simapp.BankKeeper.SendCoins(ctx, creatorAddr, pool.GetReserveAccount(), sdk.NewCoins(sdk.NewInt64Coin(DenomX, 10000))) + require.NoError(t, err) + + // Deposit request must be rejected since the pool is depleted and + // depositing coins amount is smaller than MinInitDepositAmount. + liquidity.BeginBlocker(ctx, simapp.LiquidityKeeper) + depositCoins := sdk.NewCoins(sdk.NewInt64Coin(DenomX, 10000), sdk.NewInt64Coin(DenomY, 10000)) + _, err = simapp.LiquidityKeeper.DepositLiquidityPoolToBatch(ctx, types.NewMsgDepositWithinBatch(creatorAddr, pool.Id, depositCoins)) + require.NoError(t, err) + liquidity.EndBlocker(ctx, simapp.LiquidityKeeper) + + reserveCoins = simapp.LiquidityKeeper.GetReserveCoins(ctx, pool) + require.True(t, reserveCoins.AmountOf(DenomX).Equal(sdk.NewInt(10000))) + require.True(t, reserveCoins.AmountOf(DenomY).IsZero()) + creatorCoins := simapp.BankKeeper.GetAllBalances(ctx, creatorAddr) + require.True(t, creatorCoins.AmountOf(DenomX).Equal(sdk.NewInt(990000))) + require.True(t, creatorCoins.AmountOf(DenomY).Equal(sdk.NewInt(1000000))) + + // This time the request will be accepted since depositCoins + reserveCoins > MinInitDepositAmount. + liquidity.BeginBlocker(ctx, simapp.LiquidityKeeper) + depositCoins = sdk.NewCoins(sdk.NewInt64Coin(DenomX, 990000), sdk.NewInt64Coin(DenomY, 1000000)) + _, err = simapp.LiquidityKeeper.DepositLiquidityPoolToBatch(ctx, types.NewMsgDepositWithinBatch(creatorAddr, pool.Id, depositCoins)) + require.NoError(t, err) + liquidity.EndBlocker(ctx, simapp.LiquidityKeeper) + + reserveCoins = simapp.LiquidityKeeper.GetReserveCoins(ctx, pool) + require.True(t, reserveCoins.AmountOf(DenomX).Equal(sdk.NewInt(1000000))) + require.True(t, reserveCoins.AmountOf(DenomY).Equal(sdk.NewInt(1000000))) + creatorCoins = simapp.BankKeeper.GetAllBalances(ctx, creatorAddr) + require.True(t, creatorCoins.AmountOf(DenomX).Equal(sdk.NewInt(0))) + require.True(t, creatorCoins.AmountOf(DenomY).Equal(sdk.NewInt(0))) + require.True(t, creatorCoins.AmountOf(pool.PoolCoinDenom).Equal(params.InitPoolCoinMintAmount)) +} + +func TestDepositWithCoinsSent(t *testing.T) { + simapp, ctx, pool, _, err := createTestPool(sdk.NewInt64Coin(DenomX, 1000000), sdk.NewInt64Coin(DenomY, 1000000)) + require.NoError(t, err) + + // Send extra coins to the pool reserve account, which causes the pool price to change. + // Any other coins(coins with denom "denomZ" here) than pool's reserve coins will not have any effect. + extraCoins := sdk.NewCoins( + sdk.NewInt64Coin(DenomX, 1000000), sdk.NewInt64Coin(DenomY, 2000000), sdk.NewInt64Coin("denomZ", 1000000)) + addr := app.AddRandomTestAddr(simapp, ctx, extraCoins) + err = simapp.BankKeeper.SendCoins(ctx, addr, pool.GetReserveAccount(), extraCoins) + require.NoError(t, err) + reserveCoins := simapp.LiquidityKeeper.GetReserveCoins(ctx, pool) + require.Len(t, reserveCoins, 2) // denomZ coins are ignored + require.True(t, reserveCoins.AmountOf(DenomX).Equal(sdk.NewInt(2000000))) + require.True(t, reserveCoins.AmountOf(DenomY).Equal(sdk.NewInt(3000000))) + + // Add more coins to deposit. + depositCoins := sdk.NewCoins(sdk.NewInt64Coin(DenomX, 3000000), sdk.NewInt64Coin(DenomY, 3000000)) + err = simapp.BankKeeper.AddCoins(ctx, addr, depositCoins) + require.NoError(t, err) + + liquidity.BeginBlocker(ctx, simapp.LiquidityKeeper) + _, err = simapp.LiquidityKeeper.DepositLiquidityPoolToBatch(ctx, types.NewMsgDepositWithinBatch(addr, pool.Id, depositCoins)) + require.NoError(t, err) + liquidity.EndBlocker(ctx, simapp.LiquidityKeeper) + + reserveCoins = simapp.LiquidityKeeper.GetReserveCoins(ctx, pool) + require.True(t, reserveCoins.AmountOf(DenomX).Equal(sdk.NewInt(4000000))) + require.True(t, reserveCoins.AmountOf(DenomY).Equal(sdk.NewInt(6000000))) + balances := simapp.BankKeeper.GetAllBalances(ctx, addr) + require.True(t, balances.AmountOf(DenomX).Equal(sdk.NewInt(1000000))) + require.True(t, balances.AmountOf(DenomY).Equal(sdk.NewInt(0))) + require.True(t, balances.AmountOf(pool.PoolCoinDenom).Equal(sdk.NewInt(1000000))) +} diff --git a/x/liquidity/keeper/swap.go b/x/liquidity/keeper/swap.go index 44d658644..a46c65173 100644 --- a/x/liquidity/keeper/swap.go +++ b/x/liquidity/keeper/swap.go @@ -23,9 +23,11 @@ func (k Keeper) SwapExecution(ctx sdk.Context, liquidityPoolBatch types.PoolBatc currentHeight := ctx.BlockHeight() // set executed states of all messages to true + executedMsgCount := uint64(0) var swapMsgStatesNotToBeDeleted []*types.SwapMsgState for _, sms := range swapMsgStates { sms.Executed = true + executedMsgCount++ if currentHeight > sms.OrderExpiryHeight { sms.ToBeDeleted = true } @@ -54,8 +56,6 @@ func (k Keeper) SwapExecution(ctx sdk.Context, liquidityPoolBatch types.PoolBatc // check orderbook validity and compute batchResult(direction, swapPrice, ..) result, found := orderBook.Match(X, Y) - executedMsgCount := uint64(len(swapMsgStates)) - if !found { err := k.RefundSwaps(ctx, pool, swapMsgStates) return executedMsgCount, err diff --git a/x/liquidity/keeper/swap_test.go b/x/liquidity/keeper/swap_test.go index 53ba08a81..bc755e7a1 100644 --- a/x/liquidity/keeper/swap_test.go +++ b/x/liquidity/keeper/swap_test.go @@ -339,6 +339,29 @@ func TestRefundEscrow(t *testing.T) { } } +func TestSwapWithDepletedPool(t *testing.T) { + simapp, ctx, pool, creatorAddr, err := createTestPool(sdk.NewInt64Coin(DenomX, 1000000), sdk.NewInt64Coin(DenomY, 1000000)) + require.NoError(t, err) + params := simapp.LiquidityKeeper.GetParams(ctx) + + liquidity.BeginBlocker(ctx, simapp.LiquidityKeeper) + pc := simapp.BankKeeper.GetBalance(ctx, creatorAddr, pool.PoolCoinDenom) + _, err = simapp.LiquidityKeeper.WithdrawLiquidityPoolToBatch(ctx, types.NewMsgWithdrawWithinBatch(creatorAddr, pool.Id, pc)) + require.NoError(t, err) + liquidity.EndBlocker(ctx, simapp.LiquidityKeeper) + + addr := app.AddRandomTestAddr(simapp, ctx, sdk.NewCoins(sdk.NewInt64Coin(DenomX, 100000))) + offerCoin := sdk.NewInt64Coin(DenomX, 10000) + orderPrice := sdk.MustNewDecFromStr("1.0") + liquidity.BeginBlocker(ctx, simapp.LiquidityKeeper) + _, err = simapp.LiquidityKeeper.SwapLiquidityPoolToBatch( + ctx, + types.NewMsgSwapWithinBatch(addr, pool.Id, types.DefaultSwapTypeId, offerCoin, DenomY, orderPrice, params.SwapFeeRate), + 0) + require.Error(t, err) + liquidity.EndBlocker(ctx, simapp.LiquidityKeeper) +} + func createPool(simapp *app.LiquidityApp, ctx sdk.Context, X, Y sdk.Int, denomX, denomY string) (types.Pool, error) { params := simapp.LiquidityKeeper.GetParams(ctx)