Skip to content

Commit

Permalink
Merge pull request #91 from b-harvest/fix-empty-incorrect-pool-case
Browse files Browse the repository at this point in the history
Fix empty, incorrect pool cases
  • Loading branch information
dongsam authored Dec 10, 2020
2 parents 527ea2e + 52caf9d commit 6202279
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 44 deletions.
139 changes: 100 additions & 39 deletions x/liquidity/keeper/liquidity_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ func (k Keeper) ValidateMsgCreateLiquidityPool(ctx sdk.Context, msg *types.MsgCr
return types.ErrBadOrderingReserveCoin
}

if denomA == denomB {
return types.ErrEqualDenom
}

poolKey := types.GetPoolKey(msg.ReserveCoinDenoms, msg.PoolTypeIndex)
reserveAcc := types.GetPoolReserveAcc(poolKey)
_, found := k.GetLiquidityPoolByReserveAccIndex(ctx, reserveAcc)
Expand Down Expand Up @@ -133,7 +137,7 @@ func (k Keeper) CreateLiquidityPool(ctx sdk.Context, msg *types.MsgCreateLiquidi

PoolCoinDenom := types.GetPoolCoinDenom(reserveAcc)

liquidityPool := types.LiquidityPool{
pool := types.LiquidityPool{
//PoolId: will set on SetLiquidityPoolAtomic
PoolTypeIndex: msg.PoolTypeIndex,
ReserveCoinDenoms: reserveCoinDenoms,
Expand All @@ -142,7 +146,7 @@ func (k Keeper) CreateLiquidityPool(ctx sdk.Context, msg *types.MsgCreateLiquidi
}

batchEscrowAcc := k.accountKeeper.GetModuleAddress(types.ModuleName)
mintPoolCoin := sdk.NewCoins(sdk.NewCoin(liquidityPool.PoolCoinDenom, params.InitPoolCoinMintAmount))
mintPoolCoin := sdk.NewCoins(sdk.NewCoin(pool.PoolCoinDenom, params.InitPoolCoinMintAmount))
if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, mintPoolCoin); err != nil {
return err
}
Expand All @@ -166,23 +170,24 @@ func (k Keeper) CreateLiquidityPool(ctx sdk.Context, msg *types.MsgCreateLiquidi
return err
}

liquidityPool = k.SetLiquidityPoolAtomic(ctx, liquidityPool)
batch := types.NewLiquidityPoolBatch(liquidityPool.PoolId, 1)
pool = k.SetLiquidityPoolAtomic(ctx, pool)
batch := types.NewLiquidityPoolBatch(pool.PoolId, 1)

k.SetLiquidityPoolBatch(ctx, batch)

// TODO: remove result state check, debugging
reserveCoins := k.GetReserveCoins(ctx, liquidityPool)
reserveCoins := k.GetReserveCoins(ctx, pool)
lastReserveRatio := sdk.NewDecFromInt(reserveCoins[0].Amount).Quo(sdk.NewDecFromInt(reserveCoins[1].Amount))
logger := k.Logger(ctx)
logger.Info("createPool", msg, "pool", liquidityPool, "reserveCoins", reserveCoins, "lastReserveRatio", lastReserveRatio)
logger.Info("createPool", msg, "pool", pool, "reserveCoins", reserveCoins, "lastReserveRatio", lastReserveRatio)
return nil
}

// Get reserve Coin from the liquidity pool
func (k Keeper) GetReserveCoins(ctx sdk.Context, pool types.LiquidityPool) (reserveCoins sdk.Coins) {
reserveAcc := pool.GetReserveAccount()
for _, denom := range pool.ReserveCoinDenoms {
reserveCoins = reserveCoins.Add(k.bankKeeper.GetBalance(ctx, pool.GetReserveAccount(), denom))
reserveCoins = reserveCoins.Add(k.bankKeeper.GetBalance(ctx, reserveAcc, denom))
}
return
}
Expand Down Expand Up @@ -247,12 +252,6 @@ func (k Keeper) ValidateMsgDepositLiquidityPool(ctx sdk.Context, msg types.MsgDe
return types.ErrPoolNotExists
}

for _, coin := range msg.DepositCoins {
if !types.StringInSlice(coin.Denom, pool.ReserveCoinDenoms) {
return types.ErrInvalidDenom
}
}

if msg.DepositCoins.Len() != len(pool.ReserveCoinDenoms) {
return types.ErrNumOfReserveCoin
}
Expand All @@ -264,6 +263,10 @@ func (k Keeper) ValidateMsgDepositLiquidityPool(ctx sdk.Context, msg types.MsgDe
}
// TODO: validate msgIndex

denomA, denomB := types.AlphabeticalDenomPair(msg.DepositCoins[0].Denom, msg.DepositCoins[1].Denom)
if denomA != pool.ReserveCoinDenoms[0] || denomB != pool.ReserveCoinDenoms[1] {
return types.ErrNotMatchedReserveCoin
}
return nil
}

Expand All @@ -282,7 +285,47 @@ func (k Keeper) DepositLiquidityPool(ctx sdk.Context, msg types.BatchPoolDeposit
return types.ErrPoolNotExists
}

var inputs []banktypes.Input
var outputs []banktypes.Output

batchEscrowAcc := k.accountKeeper.GetModuleAddress(types.ModuleName)
reserveAcc := pool.GetReserveAccount()
depositor := msg.Msg.GetDepositor()
params := k.GetParams(ctx)

reserveCoins := k.GetReserveCoins(ctx, pool)

// case of reserve coins has run out, ReinitializePool
if reserveCoins.IsZero() {
mintPoolCoin := sdk.NewCoins(sdk.NewCoin(pool.PoolCoinDenom, params.InitPoolCoinMintAmount))
if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, mintPoolCoin); err != nil {
return err
}
inputs = append(inputs, banktypes.NewInput(batchEscrowAcc, msg.Msg.DepositCoins))
outputs = append(outputs, banktypes.NewOutput(reserveAcc, msg.Msg.DepositCoins))

inputs = append(inputs, banktypes.NewInput(batchEscrowAcc, mintPoolCoin))
outputs = append(outputs, banktypes.NewOutput(depositor, mintPoolCoin))

// execute multi-send
if err := k.bankKeeper.InputOutputCoins(ctx, inputs, outputs); err != nil {
return err
}

msg.Succeed = true
msg.ToDelete = true
k.SetLiquidityPoolBatchDepositMsg(ctx, msg.Msg.PoolId, msg)

// TODO: remove result state check, debugging
reserveCoins := k.GetReserveCoins(ctx, pool)
lastReserveRatio := sdk.NewDecFromInt(reserveCoins[0].Amount).Quo(sdk.NewDecFromInt(reserveCoins[1].Amount))
logger := k.Logger(ctx)
logger.Info("ReinitializePool", msg, "pool", pool, "reserveCoins", reserveCoins, "lastReserveRatio", lastReserveRatio)
return nil
} else if reserveCoins.Len() != msg.Msg.DepositCoins.Len() {
return types.ErrNumOfReserveCoin
}

reserveCoins.Sort()

coinA := depositCoins[0]
Expand All @@ -292,12 +335,6 @@ func (k Keeper) DepositLiquidityPool(ctx sdk.Context, msg types.BatchPoolDeposit
depositableAmount := coinB.Amount.ToDec().Mul(lastReserveRatio).TruncateInt()
depositableAmountA := coinA.Amount
depositableAmountB := coinB.Amount
var inputs []banktypes.Input
var outputs []banktypes.Output

batchEscrowAcc := k.accountKeeper.GetModuleAddress(types.ModuleName)
reserveAcc := pool.GetReserveAccount()
depositor := msg.Msg.GetDepositor()

if coinA.Amount.LT(depositableAmount) {
depositableAmountB = coinA.Amount.ToDec().Quo(lastReserveRatio).TruncateInt()
Expand Down Expand Up @@ -330,22 +367,22 @@ func (k Keeper) DepositLiquidityPool(ctx sdk.Context, msg types.BatchPoolDeposit
outputs = append(outputs, banktypes.NewOutput(reserveAcc, sdk.NewCoins(coinB)))
}

// execute multi-send
if err := k.bankKeeper.InputOutputCoins(ctx, inputs, outputs); err != nil {
return err
}

// calculate pool token mint amount
poolCoinAmt := k.GetPoolCoinTotalSupply(ctx, pool).Mul(depositableAmountA).Quo(reserveCoins[0].Amount) // TODO: coinA after executed ?
poolCoin := sdk.NewCoins(sdk.NewCoin(pool.PoolCoinDenom, poolCoinAmt))
mintPoolCoin := sdk.NewCoins(sdk.NewCoin(pool.PoolCoinDenom, poolCoinAmt))

// mint pool token to Depositor
if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, poolCoin); err != nil {
if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, mintPoolCoin); err != nil {
panic(err)
}
if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, depositor, poolCoin); err != nil {
panic(err)
inputs = append(inputs, banktypes.NewInput(batchEscrowAcc, mintPoolCoin))
outputs = append(outputs, banktypes.NewOutput(depositor, mintPoolCoin))

// execute multi-send
if err := k.bankKeeper.InputOutputCoins(ctx, inputs, outputs); err != nil {
return err
}

msg.Succeed = true
msg.ToDelete = true
k.SetLiquidityPoolBatchDepositMsg(ctx, msg.Msg.PoolId, msg)
Expand All @@ -363,7 +400,19 @@ func (k Keeper) ValidateMsgWithdrawLiquidityPool(ctx sdk.Context, msg types.MsgW
if err := msg.ValidateBasic(); err != nil {
return err
}
// TODO: add validate logic
pool, found := k.GetLiquidityPool(ctx, msg.PoolId)
if !found {
return types.ErrPoolNotExists
}

if msg.PoolCoin.Denom != pool.PoolCoinDenom {
return types.ErrBadPoolCoinDenom
}

poolCoinTotalSupply := k.GetPoolCoinTotalSupply(ctx, pool)
if msg.PoolCoin.Amount.GT(poolCoinTotalSupply) {
return types.ErrBadPoolCoinAmount
}
return nil
}

Expand All @@ -375,6 +424,12 @@ func (k Keeper) ValidateMsgSwap(ctx sdk.Context, msg types.MsgSwap) error {
if !found {
return types.ErrPoolNotExists
}

denomA, denomB := types.AlphabeticalDenomPair(msg.OfferCoin.Denom, msg.DemandCoinDenom)
if denomA != pool.ReserveCoinDenoms[0] || denomB != pool.ReserveCoinDenoms[1] {
return types.ErrNotMatchedReserveCoin
}

// 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)
maximumOrderableAmt := reserveCoinAmt.ToDec().Mul(types.GetMaxOrderRatio()).TruncateInt()
Expand Down Expand Up @@ -408,37 +463,43 @@ func (k Keeper) WithdrawLiquidityPool(ctx sdk.Context, msg types.BatchPoolWithdr
var inputs []banktypes.Input
var outputs []banktypes.Output

reserveAcc := pool.GetReserveAccount()
withdrawer := msg.Msg.GetWithdrawer()

for _, reserveCoin := range reserveCoins {
withdrawAmt := reserveCoin.Amount.Mul(poolCoin.Amount).Quo(totalSupply)
inputs = append(inputs, banktypes.NewInput(pool.GetReserveAccount(),
inputs = append(inputs, banktypes.NewInput(reserveAcc,
sdk.NewCoins(sdk.NewCoin(reserveCoin.Denom, withdrawAmt))))
outputs = append(outputs, banktypes.NewOutput(msg.Msg.GetWithdrawer(),
outputs = append(outputs, banktypes.NewOutput(withdrawer,
sdk.NewCoins(sdk.NewCoin(reserveCoin.Denom, withdrawAmt))))
}

// execute multi-send
if err := k.bankKeeper.InputOutputCoins(ctx, inputs, outputs); err != nil {
return err
}
if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, k.accountKeeper.GetModuleAddress(types.ModuleName),
types.ModuleName, poolCoins); err != nil {
panic(err)
}

// burn the escrowed pool coins
if err := k.bankKeeper.BurnCoins(ctx, types.ModuleName, poolCoins); err != nil {
panic(err)
}

msg.Succeed = true
msg.ToDelete = true
k.SetLiquidityPoolBatchWithdrawMsg(ctx, msg.Msg.PoolId, msg)
// TODO: add events for batch result, each err cases

// TODO: remove result state check, debugging
reserveCoins = k.GetReserveCoins(ctx, pool)
if !reserveCoins.Empty() {
lastReserveRatio := sdk.NewDecFromInt(reserveCoins[0].Amount).Quo(sdk.NewDecFromInt(reserveCoins[1].Amount))
logger := k.Logger(ctx)
logger.Info("withdraw", msg, "pool", pool, "inputs", inputs, "outputs", outputs, "reserveCoins", reserveCoins, "lastReserveRatio", lastReserveRatio)

var lastReserveRatio sdk.Dec
if reserveCoins.Empty() {
lastReserveRatio = sdk.ZeroDec()
} else {
lastReserveRatio = sdk.NewDecFromInt(reserveCoins[0].Amount).Quo(sdk.NewDecFromInt(reserveCoins[1].Amount))
}
logger := k.Logger(ctx)
logger.Info("withdraw", msg, "pool", pool, "inputs", inputs, "outputs", outputs, "reserveCoins", reserveCoins, "lastReserveRatio", lastReserveRatio)
return nil
}

Expand Down
82 changes: 82 additions & 0 deletions x/liquidity/keeper/liquidity_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,88 @@ func TestWithdrawLiquidityPool(t *testing.T) {

}

func TestReinitializePool(t *testing.T) {
simapp, ctx := createTestInput()
simapp.LiquidityKeeper.SetParams(ctx, types.DefaultParams())
params := simapp.LiquidityKeeper.GetParams(ctx)

poolTypeIndex := types.DefaultPoolTypeIndex
addrs := app.AddTestAddrs(simapp, ctx, 3, params.LiquidityPoolCreationFee)

denomA := "uETH"
denomB := "uUSD"
denomA, denomB = types.AlphabeticalDenomPair(denomA, denomB)

denoms := []string{denomA, denomB}

deposit := sdk.NewCoins(sdk.NewCoin(denomA, sdk.NewInt(100*1000000)), sdk.NewCoin(denomB, sdk.NewInt(100*1000000)))
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)

require.Equal(t, deposit, depositBalance)

createMsg := types.NewMsgCreateLiquidityPool(addrs[0], poolTypeIndex, denoms, depositBalance)

err := simapp.LiquidityKeeper.CreateLiquidityPool(ctx, createMsg)
require.NoError(t, err)

lpList := simapp.LiquidityKeeper.GetAllLiquidityPools(ctx)
lp := lpList[0]

poolCoinBefore := simapp.LiquidityKeeper.GetPoolCoinTotalSupply(ctx, lp)
withdrawerPoolCoinBefore := simapp.BankKeeper.GetBalance(ctx, addrs[0], lp.PoolCoinDenom)

reserveCoins := simapp.LiquidityKeeper.GetReserveCoins(ctx, lp)
require.True(t, reserveCoins.IsEqual(deposit))

fmt.Println(poolCoinBefore, withdrawerPoolCoinBefore.Amount)
require.Equal(t, poolCoinBefore, withdrawerPoolCoinBefore.Amount)
withdrawMsg := types.NewMsgWithdrawFromLiquidityPool(addrs[0], lp.PoolId, sdk.NewCoin(lp.PoolCoinDenom, poolCoinBefore))

err = simapp.LiquidityKeeper.WithdrawLiquidityPoolToBatch(ctx, withdrawMsg)
require.NoError(t, err)

poolBatch, found := simapp.LiquidityKeeper.GetLiquidityPoolBatch(ctx, withdrawMsg.PoolId)
require.True(t, found)
msgs := simapp.LiquidityKeeper.GetAllLiquidityPoolBatchWithdrawMsgs(ctx, poolBatch)
require.Equal(t, 1, len(msgs))

err = simapp.LiquidityKeeper.WithdrawLiquidityPool(ctx, msgs[0])
require.NoError(t, err)

poolCoinAfter := simapp.LiquidityKeeper.GetPoolCoinTotalSupply(ctx, lp)
withdrawerPoolCoinAfter := simapp.BankKeeper.GetBalance(ctx, addrs[0], lp.PoolCoinDenom)
require.True(t, true, poolCoinAfter.IsZero())
require.True(t, true, withdrawerPoolCoinAfter.IsZero())
withdrawerDenomAbalance := simapp.BankKeeper.GetBalance(ctx, addrs[0], lp.ReserveCoinDenoms[0])
withdrawerDenomBbalance := simapp.BankKeeper.GetBalance(ctx, addrs[0], lp.ReserveCoinDenoms[1])
require.Equal(t, deposit.AmountOf(lp.ReserveCoinDenoms[0]), withdrawerDenomAbalance.Amount)
require.Equal(t, deposit.AmountOf(lp.ReserveCoinDenoms[1]), withdrawerDenomBbalance.Amount)

reserveCoins = simapp.LiquidityKeeper.GetReserveCoins(ctx, lp)
require.True(t, reserveCoins.IsZero())

depositMsg := types.NewMsgDepositToLiquidityPool(addrs[0], lp.PoolId, deposit)
err = simapp.LiquidityKeeper.DepositLiquidityPoolToBatch(ctx, depositMsg)
require.NoError(t, err)

depositMsgs := simapp.LiquidityKeeper.GetAllLiquidityPoolBatchDepositMsgs(ctx, poolBatch)
require.Equal(t, 1, len(depositMsgs))

err = simapp.LiquidityKeeper.DepositLiquidityPool(ctx, depositMsgs[0])
require.NoError(t, err)

poolCoin := simapp.LiquidityKeeper.GetPoolCoinTotalSupply(ctx, lp)
depositorBalance := simapp.BankKeeper.GetBalance(ctx, addrs[0], lp.PoolCoinDenom)
require.Equal(t, poolCoin, depositorBalance.Amount)

reserveCoins = simapp.LiquidityKeeper.GetReserveCoins(ctx, lp)
require.True(t, reserveCoins.IsEqual(deposit))
}

func TestGetLiquidityPoolMetaData(t *testing.T) {
simapp, ctx := createTestInput()
simapp.LiquidityKeeper.SetParams(ctx, types.DefaultParams())
Expand Down
4 changes: 3 additions & 1 deletion x/liquidity/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ func (k msgServer) WithdrawFromLiquidityPool(goCtx context.Context, msg *types.M
// Message server, handler for MsgSwap
func (k msgServer) Swap(goCtx context.Context, msg *types.MsgSwap) (*types.MsgSwapResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
k.Keeper.SwapLiquidityPoolToBatch(ctx, msg, 0)
if _, err := k.Keeper.SwapLiquidityPoolToBatch(ctx, msg, 0); err != nil {
return &types.MsgSwapResponse{}, err
}
return &types.MsgSwapResponse{}, nil
}
1 change: 1 addition & 0 deletions x/liquidity/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ var (
ErrBadBatchMsgIndex = sdkerrors.Register(ModuleName, 32, "bad msg index of the batch")
ErrSwapTypeNotExists = sdkerrors.Register(ModuleName, 33, "swap type not exists")
ErrLessThanMinOfferAmount = sdkerrors.Register(ModuleName, 34, "offer amount should over 1000 micro")
ErrNotMatchedReserveCoin = sdkerrors.Register(ModuleName, 35, "does not match the reserve coin of the pool")
)
Loading

0 comments on commit 6202279

Please sign in to comment.