Skip to content

Commit

Permalink
Fixing close position for non-zero exit fee pool (#685)
Browse files Browse the repository at this point in the history
* Fixing close position for non-zero exit fee pool

* Fixing query exit pool estimation, position health calc and adding unit test cases

* Fixing test cases

* Fixing CheckAmmPoolUsdcBalance incorrect comparison (amount to value)

* Fixing test cases
  • Loading branch information
avkr003 authored Aug 2, 2024
1 parent fdedffd commit e0876fd
Show file tree
Hide file tree
Showing 16 changed files with 397 additions and 106 deletions.
30 changes: 15 additions & 15 deletions x/amm/keeper/apply_exit_pool_state_change.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,45 @@ import (
"github.com/elys-network/elys/x/amm/types"
)

func (k Keeper) ApplyExitPoolStateChange(ctx sdk.Context, pool types.Pool, exiter sdk.AccAddress, numShares math.Int, exitCoins sdk.Coins) error {
func (k Keeper) ApplyExitPoolStateChange(ctx sdk.Context, pool types.Pool, exiter sdk.AccAddress, numShares math.Int, exitCoins sdk.Coins) (sdk.Coins, error) {
// Withdraw exit amount of token from commitment module to exiter's wallet.
poolShareDenom := types.GetPoolShareDenom(pool.GetPoolId())

// Withdraw committed LP tokens
err := k.commitmentKeeper.UncommitTokens(ctx, exiter, poolShareDenom, numShares)
if err != nil {
return err
return sdk.Coins{}, err
}

if err := k.bankKeeper.SendCoins(ctx, sdk.MustAccAddressFromBech32(pool.GetAddress()), exiter, exitCoins); err != nil {
return err
if err = k.bankKeeper.SendCoins(ctx, sdk.MustAccAddressFromBech32(pool.GetAddress()), exiter, exitCoins); err != nil {
return sdk.Coins{}, err
}

exitFeeCoins := PortionCoins(exitCoins, pool.PoolParams.ExitFee)
rebalanceTreasury := sdk.MustAccAddressFromBech32(pool.GetRebalanceTreasury())
if err := k.bankKeeper.SendCoins(ctx, exiter, rebalanceTreasury, exitFeeCoins); err != nil {
return err
if err = k.bankKeeper.SendCoins(ctx, exiter, rebalanceTreasury, exitFeeCoins); err != nil {
return sdk.Coins{}, err
}

if err := k.OnCollectFee(ctx, pool, exitFeeCoins); err != nil {
return err
if err = k.OnCollectFee(ctx, pool, exitFeeCoins); err != nil {
return sdk.Coins{}, err
}

if err := k.BurnPoolShareFromAccount(ctx, pool, exiter, numShares); err != nil {
return err
if err = k.BurnPoolShareFromAccount(ctx, pool, exiter, numShares); err != nil {
return sdk.Coins{}, err
}

if err := k.SetPool(ctx, pool); err != nil {
return err
if err = k.SetPool(ctx, pool); err != nil {
return sdk.Coins{}, err
}

types.EmitRemoveLiquidityEvent(ctx, exiter, pool.GetPoolId(), exitCoins)
if k.hooks != nil {
err := k.hooks.AfterExitPool(ctx, exiter, pool, numShares, exitCoins)
err = k.hooks.AfterExitPool(ctx, exiter, pool, numShares, exitCoins)
if err != nil {
return err
return sdk.Coins{}, err
}
}
k.RecordTotalLiquidityDecrease(ctx, exitCoins)
return nil
return exitCoins.Sub(exitFeeCoins...), nil
}
2 changes: 1 addition & 1 deletion x/amm/keeper/apply_exit_pool_state_change_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,6 @@ func (suite *KeeperTestSuite) TestApplyExitPoolStateChange_WithdrawFromCommitmen
suite.Require().True(lpTokenBalance.Amount.Equal(sdk.ZeroInt()))

ctx = ctx.WithBlockTime(ctx.BlockTime().Add(time.Hour))
err = app.AmmKeeper.ApplyExitPoolStateChange(ctx, pool, addrs[0], pool.TotalShares.Amount, coins)
_, err = app.AmmKeeper.ApplyExitPoolStateChange(ctx, pool, addrs[0], pool.TotalShares.Amount, coins)
suite.Require().NoError(err)
}
18 changes: 9 additions & 9 deletions x/amm/keeper/keeper_exit_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,35 @@ func (k Keeper) ExitPool(
shareInAmount math.Int,
tokenOutMins sdk.Coins,
tokenOutDenom string,
) (exitCoins sdk.Coins, err error) {
) (exitCoins, exitCoinsAfterExitFee sdk.Coins, err error) {
pool, poolExists := k.GetPool(ctx, poolId)
if !poolExists {
return sdk.Coins{}, types.ErrInvalidPoolId
return sdk.Coins{}, sdk.Coins{}, types.ErrInvalidPoolId
}

totalSharesAmount := pool.GetTotalShares()
if shareInAmount.GTE(totalSharesAmount.Amount) {
return sdk.Coins{}, errorsmod.Wrapf(types.ErrInvalidMathApprox, "Trying to exit >= the number of shares contained in the pool.")
return sdk.Coins{}, sdk.Coins{}, errorsmod.Wrapf(types.ErrInvalidMathApprox, "Trying to exit >= the number of shares contained in the pool.")
} else if shareInAmount.LTE(sdk.ZeroInt()) {
return sdk.Coins{}, errorsmod.Wrapf(types.ErrInvalidMathApprox, "Trying to exit a negative amount of shares")
return sdk.Coins{}, sdk.Coins{}, errorsmod.Wrapf(types.ErrInvalidMathApprox, "Trying to exit a negative amount of shares")
}
exitCoins, err = pool.ExitPool(ctx, k.oracleKeeper, k.accountedPoolKeeper, shareInAmount, tokenOutDenom)
if err != nil {
return sdk.Coins{}, err
return sdk.Coins{}, sdk.Coins{}, err
}
if !tokenOutMins.DenomsSubsetOf(exitCoins) || tokenOutMins.IsAnyGT(exitCoins) {
return sdk.Coins{}, errorsmod.Wrapf(types.ErrLimitMinAmount,
return sdk.Coins{}, sdk.Coins{}, errorsmod.Wrapf(types.ErrLimitMinAmount,
"Exit pool returned %s , minimum tokens out specified as %s",
exitCoins, tokenOutMins)
}

err = k.ApplyExitPoolStateChange(ctx, pool, sender, shareInAmount, exitCoins)
exitCoinsAfterExitFee, err = k.ApplyExitPoolStateChange(ctx, pool, sender, shareInAmount, exitCoins)
if err != nil {
return sdk.Coins{}, err
return sdk.Coins{}, sdk.Coins{}, err
}

// Decrease liquidty amount
k.RecordTotalLiquidityDecrease(ctx, exitCoins)

return exitCoins, nil
return exitCoins, exitCoinsAfterExitFee, nil
}
2 changes: 1 addition & 1 deletion x/amm/keeper/msg_server_exit_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func (k msgServer) ExitPool(goCtx context.Context, msg *types.MsgExitPool) (*typ
return nil, err
}

exitCoins, err := k.Keeper.ExitPool(ctx, sender, msg.PoolId, msg.ShareAmountIn, msg.MinAmountsOut, msg.TokenOutDenom)
exitCoins, _, err := k.Keeper.ExitPool(ctx, sender, msg.PoolId, msg.ShareAmountIn, msg.MinAmountsOut, msg.TokenOutDenom)
if err != nil {
return nil, err
}
Expand Down
4 changes: 3 additions & 1 deletion x/amm/keeper/query_exit_pool_estimation.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,7 @@ func (k Keeper) ExitPoolEst(
return sdk.Coins{}, math.LegacyZeroDec(), err
}

return exitCoins, weightBalanceBonus, nil
exitFeeCoins := PortionCoins(exitCoins, pool.PoolParams.ExitFee)

return exitCoins.Sub(exitFeeCoins...), weightBalanceBonus, nil
}
6 changes: 3 additions & 3 deletions x/leveragelp/keeper/begin_blocker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ func (suite KeeperTestSuite) TestBeginBlocker() {
health, err := k.GetPositionHealth(suite.ctx, *position)
suite.Require().NoError(err)
// suite.Require().Equal(health.String(), "1.221000000000000000") // slippage enabled on amm
suite.Require().Equal(health.String(), "1.250000000000000000") // slippage disabled on amm
suite.Require().Equal("1.250000000000000000", health.String()) // slippage disabled on amm

suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour * 24 * 500))
suite.app.StablestakeKeeper.BeginBlocker(suite.ctx)
suite.app.StablestakeKeeper.UpdateInterestStackedByAddress(suite.ctx, sdk.AccAddress(position.GetPositionAddress()))
health, err = k.GetPositionHealth(suite.ctx, *position)
suite.Require().NoError(err)
// suite.Require().Equal(health.String(), "1.024543738200125865") // slippage enabled on amm
suite.Require().Equal(health.String(), "1.025220422390814025") // slippage disabled on amm
suite.Require().Equal("1.048855698433009587", health.String()) // slippage disabled on amm

params := k.GetParams(suite.ctx)
params.SafetyFactor = sdk.NewDecWithPrec(11, 1)
Expand All @@ -57,7 +57,7 @@ func (suite KeeperTestSuite) TestLiquidatePositionIfUnhealthy() {
health, err = k.GetPositionHealth(suite.ctx, *position)
suite.Require().NoError(err)
// suite.Require().Equal(health.String(), "1.024543738200125865") // slippage enabled on amm
suite.Require().Equal(health.String(), "1.025220422390814025") // slippage disabled on amm
suite.Require().Equal("1.048855698433009587", health.String()) // slippage disabled on amm

cacheCtx, _ := suite.ctx.CacheContext()
params := k.GetParams(cacheCtx)
Expand Down
8 changes: 4 additions & 4 deletions x/leveragelp/keeper/hooks_amm.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ func (k Keeper) CheckAmmPoolUsdcBalance(ctx sdk.Context, ammPool ammtypes.Pool)
Quo(sdk.NewDecFromInt(ammPool.TotalShares.Amount))

depositDenom := k.stableKeeper.GetDepositDenom(ctx)
price := k.oracleKeeper.GetAssetPriceFromDenom(ctx, depositDenom)

for _, asset := range ammPool.PoolAssets {
if asset.Token.Denom == depositDenom {
if asset.Token.Amount.LT(leverageLpTvl.RoundInt()) {
return types.ErrInsufficientUsdcAfterOp
}
if asset.Token.Denom == depositDenom && price.MulInt(asset.Token.Amount).LT(leverageLpTvl) {
return types.ErrInsufficientUsdcAfterOp
}
}
return nil
Expand Down
189 changes: 139 additions & 50 deletions x/leveragelp/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper_test

import (
"testing"
"time"

"github.com/cometbft/cometbft/crypto/ed25519"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
Expand All @@ -15,10 +16,41 @@ import (
"github.com/stretchr/testify/suite"
)

type assetPriceInfo struct {
denom string
display string
price sdk.Dec
}

const (
initChain = true
)

var (
priceMap = map[string]assetPriceInfo{
"uusdc": {
denom: ptypes.BaseCurrency,
display: "USDC",
price: sdk.OneDec(),
},
"uusdt": {
denom: "uusdt",
display: "USDT",
price: sdk.OneDec(),
},
"uelys": {
denom: ptypes.Elys,
display: "ELYS",
price: sdk.MustNewDecFromStr("3.0"),
},
"uatom": {
denom: ptypes.ATOM,
display: "ATOM",
price: sdk.MustNewDecFromStr("6.0"),
},
}
)

type KeeperTestSuite struct {
suite.Suite

Expand All @@ -35,62 +67,119 @@ func (suite *KeeperTestSuite) SetupTest() {
suite.app = app
}

func (suite *KeeperTestSuite) ResetSuite() {
suite.SetupTest()
}

func (suite *KeeperTestSuite) AddBlockTime(d time.Duration) {
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(d))
}

func (suite *KeeperTestSuite) EnableWhiteListing() {
params := suite.app.LeveragelpKeeper.GetParams(suite.ctx)
params.WhitelistingEnabled = true
err := suite.app.LeveragelpKeeper.SetParams(suite.ctx, &params)
if err != nil {
panic(err)
}
}

func (suite *KeeperTestSuite) DisableWhiteListing() {
params := suite.app.LeveragelpKeeper.GetParams(suite.ctx)
params.WhitelistingEnabled = false
err := suite.app.LeveragelpKeeper.SetParams(suite.ctx, &params)
if err != nil {
panic(err)
}
}

func (suite *KeeperTestSuite) SetMaxOpenPositions(value int64) {
params := suite.app.LeveragelpKeeper.GetParams(suite.ctx)
params.MaxOpenPositions = value
err := suite.app.LeveragelpKeeper.SetParams(suite.ctx, &params)
if err != nil {
panic(err)
}
}

func (suite *KeeperTestSuite) SetPoolThreshold(value sdk.Dec) {
params := suite.app.LeveragelpKeeper.GetParams(suite.ctx)
params.PoolOpenThreshold = value
err := suite.app.LeveragelpKeeper.SetParams(suite.ctx, &params)
if err != nil {
panic(err)
}
}

func (suite *KeeperTestSuite) SetSafetyFactor(value sdk.Dec) {
params := suite.app.LeveragelpKeeper.GetParams(suite.ctx)
params.SafetyFactor = value
err := suite.app.LeveragelpKeeper.SetParams(suite.ctx, &params)
if err != nil {
panic(err)
}
}

func (suite *KeeperTestSuite) EnablePool(poolId uint64) {
pool, found := suite.app.LeveragelpKeeper.GetPool(suite.ctx, poolId)
if !found {
panic("pool not found")
}
pool.Enabled = true
suite.app.LeveragelpKeeper.SetPool(suite.ctx, pool)
}

func (suite *KeeperTestSuite) DisablePool(poolId uint64) {
pool, found := suite.app.LeveragelpKeeper.GetPool(suite.ctx, poolId)
if !found {
panic("pool not found")
}
pool.Enabled = false
suite.app.LeveragelpKeeper.SetPool(suite.ctx, pool)
}

func TestKeeperSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}

func SetupStableCoinPrices(ctx sdk.Context, oracle oraclekeeper.Keeper) {
func SetupCoinPrices(ctx sdk.Context, oracle oraclekeeper.Keeper) {
// prices set for USDT and USDC
provider := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address())
oracle.SetAssetInfo(ctx, oracletypes.AssetInfo{
Denom: ptypes.BaseCurrency,
Display: "USDC",
Decimal: 6,
})
oracle.SetAssetInfo(ctx, oracletypes.AssetInfo{
Denom: "uusdt",
Display: "USDT",
Decimal: 6,
})
oracle.SetAssetInfo(ctx, oracletypes.AssetInfo{
Denom: ptypes.Elys,
Display: "ELYS",
Decimal: 6,
})
oracle.SetAssetInfo(ctx, oracletypes.AssetInfo{
Denom: ptypes.ATOM,
Display: "ATOM",
Decimal: 6,
})

oracle.SetPrice(ctx, oracletypes.Price{
Asset: "USDC",
Price: sdk.NewDec(1000000),
Source: "elys",
Provider: provider.String(),
Timestamp: uint64(ctx.BlockTime().Unix()),
})
oracle.SetPrice(ctx, oracletypes.Price{
Asset: "USDT",
Price: sdk.NewDec(1000000),
Source: "elys",
Provider: provider.String(),
Timestamp: uint64(ctx.BlockTime().Unix()),
})
oracle.SetPrice(ctx, oracletypes.Price{
Asset: "ELYS",
Price: sdk.NewDec(100),
Source: "elys",
Provider: provider.String(),
Timestamp: uint64(ctx.BlockTime().Unix()),
})
oracle.SetPrice(ctx, oracletypes.Price{
Asset: "ATOM",
Price: sdk.NewDec(100),
Source: "atom",
Provider: provider.String(),
Timestamp: uint64(ctx.BlockTime().Unix()),
})

for _, v := range priceMap {
oracle.SetAssetInfo(ctx, oracletypes.AssetInfo{
Denom: v.denom,
Display: v.display,
Decimal: 6,
})
oracle.SetPrice(ctx, oracletypes.Price{
Asset: v.display,
Price: v.price,
Source: "elys",
Provider: provider.String(),
Timestamp: uint64(ctx.BlockTime().Unix()),
})
}
}

func AddCoinPrices(ctx sdk.Context, oracle oraclekeeper.Keeper, denoms []string) {
// prices set for USDT and USDC
provider := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address())

for _, v := range denoms {
oracle.SetAssetInfo(ctx, oracletypes.AssetInfo{
Denom: priceMap[v].denom,
Display: priceMap[v].display,
Decimal: 6,
})
oracle.SetPrice(ctx, oracletypes.Price{
Asset: priceMap[v].display,
Price: priceMap[v].price,
Source: "elys",
Provider: provider.String(),
Timestamp: uint64(ctx.BlockTime().Unix()),
})
}
}

func TestGetAllWhitelistedAddress(t *testing.T) {
Expand Down
Loading

0 comments on commit e0876fd

Please sign in to comment.