From ead5ce92bc1da3ce3e92bc6e89c702e2d7d63657 Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Mon, 12 Aug 2024 11:01:35 -0400 Subject: [PATCH 01/21] basic test setup --- tests/dex/state_test.go | 103 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 tests/dex/state_test.go diff --git a/tests/dex/state_test.go b/tests/dex/state_test.go new file mode 100644 index 000000000..bb08fceec --- /dev/null +++ b/tests/dex/state_test.go @@ -0,0 +1,103 @@ +package dex_state_test + +import ( + "fmt" + "testing" +) + +type existingUsers int + +const ( + none existingUsers = iota + creator + oneOther +) + +type liquidityDistribution int + +const ( + TokenA1TokenB0 liquidityDistribution = iota + TokenA1TokenB1 + TokenA0TokenB0 + TokenA0TokenB1 +) + +func setupDepositState(state depositState) { + +} + +type testStates struct { + key string + states []any +} + +type depositState struct { + existingShareHolders existingUsers + liquidityDistribution liquidityDistribution +} + +var depositTestStates []testStates = []testStates{ + {key: "existingUsers", states: []any{none, creator, oneOther}}, + {key: "liquidityDistribution", states: []any{TokenA1TokenB0, + TokenA1TokenB1, + TokenA0TokenB0, + TokenA0TokenB1}}} + +// func generateAllDepositStates(testStates []testStates) []depositState { + +// } + +type DepositStateParams struct { +} + +func generatePermutations(values []testStates) map[string]any { + var result map[string]any + + // Recursive function to generate permutations + var generate func(int, []any) + generate = func(index int, current []any) { + // Base case: if we've reached the end of the values, add the current combination to the result + if index == len(values) { + // Make a copy of the current slice and add it to the result + temp := make([]any, len(current)) + copy(temp, current) + result[values[index].key] = result + return + } + + // Iterate over the elements in the current sub-array + for _, value := range values[index].states { + // Add the current value to the combination and recurse + generate(index+1, append(current, value)) + } + } + + // Start the recursion with an empty combination + generate(0, []any{}) + + return result +} + +func TestShit(t *testing.T) { + + permutations := generatePermutations(depositTestStates) + + // Print the permutations + for i, p := range permutations { + fmt.Printf("%v: %v\n", i, p) + } + t.Fail() +} + +// 1. State Conditions +// 1. Existing pool share holders +// 1. None +// 2. Creator +// 3. 1 pre-existing +// 2. Pool liquidity distribution +// 1. 1x tokenA 0x TokenB +// 2. 1x TokenA 1x TokenB +// 3. 0x TokenA 0x TokenB +// 4. 0x TokenA 1x TokenB +// 2. Assertions +// 1. Correct # of shares issued From 5714affc3ab4570c5ec49dff33b878a03f4a9eda Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Wed, 14 Aug 2024 11:13:41 -0400 Subject: [PATCH 02/21] more deposit state test --- tests/dex/state_test.go | 333 ++++++++++++++++++++++++++++++++-------- 1 file changed, 270 insertions(+), 63 deletions(-) diff --git a/tests/dex/state_test.go b/tests/dex/state_test.go index bb08fceec..1189b4a4c 100644 --- a/tests/dex/state_test.go +++ b/tests/dex/state_test.go @@ -2,102 +2,309 @@ package dex_state_test import ( "fmt" + "reflect" "testing" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/neutron-org/neutron/v4/testutil/apptesting" + "github.com/neutron-org/neutron/v4/testutil/common/sample" + dexkeeper "github.com/neutron-org/neutron/v4/x/dex/keeper" + dextypes "github.com/neutron-org/neutron/v4/x/dex/types" ) -type existingUsers int +// Shared Setup Code ////////////////////////////////////////////////////////// + +// Bools +const ( + True string = "True" + False = "False" +) +// ExistingShareHolders const ( - none existingUsers = iota - creator - oneOther + None string = "None" + Creator = "Creator" + OneOther = "OneOther" + OneOtherAndCreator = "OneOtherAndCreator" ) -type liquidityDistribution int +// LiquidityDistribution +const ( + TokenA0TokenB1 string = "TokenA0TokenB1" + TokenA0TokenB2 = "TokenA0TokenB2" + TokenA1TokenB0 = "TokenA1TokenB0" + TokenA1TokenB1 = "TokenA1TokenB1" + TokenA1TokenB2 = "TokenA1TokenB2" + TokenA2TokenB0 = "TokenA2TokenB0" + TokenA2TokenB1 = "TokenA2TokenB1" + TokenA2TokenB2 = "TokenA2TokenB2" +) const ( - TokenA1TokenB0 liquidityDistribution = iota - TokenA1TokenB1 - TokenA0TokenB0 - TokenA0TokenB1 + BaseTokenAmount = 1_000_000 + DefaultTick = 0 + DefaultFee = 1 ) -func setupDepositState(state depositState) { +var BaseTokenAmountInt = math.NewInt(BaseTokenAmount) +type testParams struct { + field string + states []string } -type testStates struct { - key string - states []any +type LiquidityDistribution struct { + TokenA sdk.Coin + TokenB sdk.Coin } -type depositState struct { - existingShareHolders existingUsers - liquidityDistribution liquidityDistribution +func parseLiquidityDistribution(liquidityDistribution string) LiquidityDistribution { + switch liquidityDistribution { + case TokenA0TokenB1: + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(0).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(1)).Mul(BaseTokenAmountInt)} + case TokenA0TokenB2: + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(0).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(2)).Mul(BaseTokenAmountInt)} + case TokenA1TokenB0: + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(0)).Mul(BaseTokenAmountInt)} + case TokenA1TokenB1: + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(1)).Mul(BaseTokenAmountInt)} + case TokenA1TokenB2: + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(2)).Mul(BaseTokenAmountInt)} + case TokenA2TokenB0: + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(2).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(0)).Mul(BaseTokenAmountInt)} + case TokenA2TokenB1: + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(2).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(1)).Mul(BaseTokenAmountInt)} + case TokenA2TokenB2: + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(2).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(2)).Mul(BaseTokenAmountInt)} + default: + panic("invalid liquidity distribution") + } } -var depositTestStates []testStates = []testStates{ - {key: "existingUsers", states: []any{none, creator, oneOther}}, - {key: "liquidityDistribution", states: []any{TokenA1TokenB0, - TokenA1TokenB1, - TokenA0TokenB0, - TokenA0TokenB1}}} +func parseBool(b string) bool { + switch b { + case True: + return true + case False: + return false + default: + panic("invalid bool") -// func generateAllDepositStates(testStates []testStates) []depositState { + } +} + +func splitLiquidityDistribution(liquidityDistribution LiquidityDistribution, n int64) []LiquidityDistribution { + nInt := math.NewInt(n) + amount0 := liquidityDistribution.TokenA.Amount.Quo(nInt) + amount1 := liquidityDistribution.TokenB.Amount.Quo(nInt) + + result := make([]LiquidityDistribution, n) + for i := range n { + + result[i] = LiquidityDistribution{ + TokenA: sdk.NewCoin(liquidityDistribution.TokenA.Denom, amount0), + TokenB: sdk.NewCoin(liquidityDistribution.TokenB.Denom, amount1), + } + + } -// } + return result -type DepositStateParams struct { } -func generatePermutations(values []testStates) map[string]any { - var result map[string]any +func generatePermutations[T any](stateType T, testStates []testParams) []T { + result := make([]T, 0) + + var generate func(index int, current T) + generate = func(index int, current T) { - // Recursive function to generate permutations - var generate func(int, []any) - generate = func(index int, current []any) { - // Base case: if we've reached the end of the values, add the current combination to the result - if index == len(values) { - // Make a copy of the current slice and add it to the result - temp := make([]any, len(current)) - copy(temp, current) - result[values[index].key] = result + // Base Case + if index == len(testStates) { + result = append(result, current) return } - // Iterate over the elements in the current sub-array - for _, value := range values[index].states { - // Add the current value to the combination and recurse - generate(index+1, append(current, value)) + // Iterate through all possible values and create new states + for _, value := range testStates[index].states { + fieldName := testStates[index].field + temp := current + v := reflect.ValueOf(&temp).Elem() + field := v.FieldByName(fieldName) + field.SetString(value) + generate(index+1, temp) } - } - // Start the recursion with an empty combination - generate(0, []any{}) + } + generate(0, stateType) return result } -func TestShit(t *testing.T) { +func (s *DexStateTestSuite) makeDeposit(addr sdk.AccAddress, depositAmts LiquidityDistribution, disableAutoSwap bool) (*dextypes.MsgDepositResponse, error) { + coins := sdk.NewCoins(depositAmts.TokenA, depositAmts.TokenB) + s.FundAcc(addr, coins) + + return s.msgServer.Deposit(s.Ctx, &dextypes.MsgDeposit{ + + Creator: addr.String(), + Receiver: addr.String(), + TokenA: depositAmts.TokenA.Denom, + TokenB: depositAmts.TokenB.Denom, + AmountsA: []math.Int{depositAmts.TokenA.Amount}, + AmountsB: []math.Int{depositAmts.TokenB.Amount}, + TickIndexesAToB: []int64{DefaultTick}, + Fees: []uint64{DefaultFee}, + Options: []*dextypes.DepositOptions{{DisableAutoswap: disableAutoSwap}}, + }) +} + +func (s *DexStateTestSuite) makeDepositSuccess(addr sdk.AccAddress, depositAmts LiquidityDistribution, disableAutoSwap bool) *dextypes.MsgDepositResponse { + resp, err := s.makeDeposit(addr, depositAmts, disableAutoSwap) + s.NoError(err) - permutations := generatePermutations(depositTestStates) + return resp +} + +type DexStateTestSuite struct { + apptesting.KeeperTestHelper + msgServer dextypes.MsgServer + creator sdk.AccAddress + alice sdk.AccAddress +} + +func (s *DexStateTestSuite) SetupTest() { + s.Setup() + + s.creator = sdk.MustAccAddressFromBech32(sample.AccAddress()) + s.alice = sdk.MustAccAddressFromBech32(sample.AccAddress()) + + s.msgServer = dexkeeper.NewMsgServerImpl(s.App.DexKeeper) +} - // Print the permutations - for i, p := range permutations { - fmt.Printf("%v: %v\n", i, p) +// Deposit State Test ///////////////////////////////////////////////////////// +type depositTestParams struct { + // State Conditions + ExistingShareHolders string + LiquidityDistribution string + // Message Variants + DisableAutoswap string + FailTxOnBEL string + DepositAmounts string +} + +func (s *DexStateTestSuite) setupDepositState(params depositTestParams) { + liquidityDistr := parseLiquidityDistribution(params.LiquidityDistribution) + + switch params.ExistingShareHolders { + case None: + break + case Creator: + s.makeDepositSuccess(s.creator, liquidityDistr, false) + case OneOther: + s.makeDepositSuccess(s.alice, liquidityDistr, false) + case OneOtherAndCreator: + liqDistrArr := splitLiquidityDistribution(liquidityDistr, 2) + s.makeDepositSuccess(s.creator, liqDistrArr[1], false) + s.makeDepositSuccess(s.alice, liqDistrArr[0], false) + } +} + +func CalcDepositOutput( + existingDistr, depositDistr LiquidityDistribution, +) (resultAmountA, resultAmountB math.Int) { + depositA := depositDistr.TokenA.Amount + depositB := depositDistr.TokenB.Amount + existingA := existingDistr.TokenA.Amount + existingB := existingDistr.TokenB.Amount + + switch { + //Pool is empty can deposit full amounts + case existingA.IsZero() && existingB.IsZero(): + return depositA, depositB + // Pool only has TokenB, can deposit all of depositB + case existingA.IsZero(): + return math.ZeroInt(), depositB + // Pool only has TokenA, can deposit all of depositA + case existingB.IsZero(): + return depositA, math.ZeroInt() + // Pool has a ratio of A and B, deposit must match this ratio + case existingA.IsPositive() && existingB.IsPositive(): + targetRatioA := math.LegacyNewDecFromInt(existingA).Quo(math.LegacyNewDecFromInt(existingB)) + maxAmountA := math.LegacyNewDecFromInt(depositA).Mul(targetRatioA).TruncateInt() + resultAmountA = math.MinInt(depositA, maxAmountA) + targetRatioB := math.LegacyOneDec().Quo(targetRatioA) + maxAmountB := math.LegacyNewDecFromInt(depositB).Mul(targetRatioB).TruncateInt() + resultAmountB = math.MinInt(depositB, maxAmountB) + + return resultAmountA, resultAmountB + default: + panic("unhandled deposit calc case") } - t.Fail() -} - -// 1. State Conditions -// 1. Existing pool share holders -// 1. None -// 2. Creator -// 3. 1 pre-existing -// 2. Pool liquidity distribution -// 1. 1x tokenA 0x TokenB -// 2. 1x TokenA 1x TokenB -// 3. 0x TokenA 0x TokenB -// 4. 0x TokenA 1x TokenB -// 2. Assertions -// 1. Correct # of shares issued +} + +func calcExpectedDepositAmounts(existingDistr, depositDistr LiquidityDistribution, disableAutoSwap bool) (tokenAAmount, tokenBAmount math.Int) { + + amountAWithoutAutoswap, amountBWithoutAutoswap := CalcDepositOutput(existingDistr, depositDistr) + + if disableAutoSwap { + return amountAWithoutAutoswap, amountBWithoutAutoswap + } + +} + +func (s *DexStateTestSuite) validateDepositResult(params depositTestParams, _ *dextypes.MsgDepositResponse, err error) { + // Handle case where disableAutoswap == true and deposit is imbalanced + if params.DisableAutoswap == True && params.DepositAmounts != params.LiquidityDistribution { + s.ErrorIs(err, dextypes.ErrZeroTrueDeposit) + } + +} + +func TestDeposit(t *testing.T) { + testParams := []testParams{ + {field: "ExistingShareHolders", states: []string{None, Creator, OneOther}}, + {field: "LiquidityDistribution", states: []string{ + TokenA0TokenB1, + TokenA0TokenB2, + TokenA1TokenB0, + TokenA1TokenB1, + TokenA1TokenB2, + TokenA2TokenB0, + TokenA2TokenB1, + TokenA2TokenB2, + }}, + {field: "DisableAutoswap", states: []string{True, False}}, + // {field: "FailTxOnBEL", states: []string{True, False}}, I don't think this needs to be tested + {field: "DepositAmounts", states: []string{ + TokenA0TokenB1, + TokenA0TokenB2, + TokenA1TokenB1, + TokenA1TokenB2, + TokenA2TokenB2, + }}, + } + testCases := generatePermutations(depositTestParams{}, testParams) + + for _, tc := range testCases { + testName := fmt.Sprintf("%v", tc) + t.Run(testName, func(t *testing.T) { + s := new(DexStateTestSuite) + s.setupDepositState(tc) + disableAutoSwap := parseBool(tc.DisableAutoswap) + depositAmts := parseLiquidityDistribution(tc.DepositAmounts) + + pairID := dextypes.PairID{Token0: "TokenA", Token1: "TokenB"} + poolID, found := s.App.DexKeeper.GetPoolIDByParams(s.Ctx, &pairID, DefaultTick, DefaultFee) + s.True(found, "Pool not found after deposit") + poolDenom := dextypes.NewPoolDenom(poolID) + + existingSharesOwned := s.App.BankKeeper.GetBalance(s.Ctx, s.creator, poolDenom) + resp, err := s.makeDeposit(s.creator, depositAmts, disableAutoSwap) + s.validateDepositResult(tc, resp, err) + + }) + + } + +} From 122ada0dc3e1a1cb4f87ea3b6773f27ca7acc1ec Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Thu, 15 Aug 2024 00:43:04 -0400 Subject: [PATCH 03/21] Temp: fix autoswap calculation --- x/dex/types/pool.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/x/dex/types/pool.go b/x/dex/types/pool.go index 3a167f5b4..47018aadc 100644 --- a/x/dex/types/pool.go +++ b/x/dex/types/pool.go @@ -199,7 +199,7 @@ func (p *Pool) CalcResidualSharesMinted( residualAmount0 math.Int, residualAmount1 math.Int, ) (sharesMinted sdk.Coin, err error) { - fee := CalcFee(p.UpperTick1.Key.TickIndexTakerToMaker, p.LowerTick0.Key.TickIndexTakerToMaker) + fee := p.Fee() valueMintedToken0, err := CalcResidualValue( residualAmount0, residualAmount1, @@ -270,21 +270,17 @@ func CalcGreatestMatchingRatio( func CalcResidualValue( amount0, amount1 math.Int, priceLowerTakerToMaker math_utils.PrecDec, - fee int64, + fee uint64, ) (math_utils.PrecDec, error) { - // ResidualValue = Amount0 * (Price1to0Center / Price1to0Upper) + Amount1 * Price1to0Lower - amount0Discount, err := CalcPrice(-fee) + feeInt64 := utils.MustSafeUint64ToInt64(fee) + amount0Discount, err := CalcPrice(feeInt64) if err != nil { return math_utils.ZeroPrecDec(), err } - + // ResidualValue = Amount0 * (Price1to0Center / Price1to0Upper) + Amount1 * Price1to0Lower return amount0Discount.MulInt(amount0).Add(priceLowerTakerToMaker.MulInt(amount1)), nil } -func CalcFee(upperTickIndex, lowerTickIndex int64) int64 { - return (upperTickIndex - lowerTickIndex) / 2 -} - func CalcAmountAsToken0(amount0, amount1 math.Int, price1To0 math_utils.PrecDec) math_utils.PrecDec { amount0Dec := math_utils.NewPrecDecFromInt(amount0) From 671c096fb2c2971d4085285a2d1874b4990baf88 Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Thu, 15 Aug 2024 01:58:09 -0400 Subject: [PATCH 04/21] Temp: fix autoswap share value calculation --- x/dex/keeper/core_helper_test.go | 3 ++- x/dex/types/pool.go | 41 ++++++++------------------------ 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/x/dex/keeper/core_helper_test.go b/x/dex/keeper/core_helper_test.go index c09d45d99..8ea0939d8 100644 --- a/x/dex/keeper/core_helper_test.go +++ b/x/dex/keeper/core_helper_test.go @@ -65,7 +65,8 @@ func (s *CoreHelpersTestSuite) setLPAtFee1Pool(tickIndex int64, amountA, amountB existingShares := s.app.BankKeeper.GetSupply(s.ctx, pool.GetPoolDenom()).Amount - totalShares := pool.CalcSharesMinted(amountAInt, amountBInt, existingShares) + depositAmountAsToken0 := types.CalcAmountAsToken0(amountAInt, amountBInt, pool.MustCalcPrice1To0Center()) + totalShares := pool.CalcSharesMinted(depositAmountAsToken0, existingShares) err = s.app.DexKeeper.MintShares(s.ctx, s.alice, sdk.NewCoins(totalShares)) s.Require().NoError(err) diff --git a/x/dex/types/pool.go b/x/dex/types/pool.go index 47018aadc..1571f8a00 100644 --- a/x/dex/types/pool.go +++ b/x/dex/types/pool.go @@ -129,7 +129,7 @@ func (p *Pool) Deposit( return math.ZeroInt(), math.ZeroInt(), sdk.Coin{Denom: p.GetPoolDenom()} } - outShares = p.CalcSharesMinted(inAmount0, inAmount1, existingShares) + depositValueAsToken0 := CalcAmountAsToken0(inAmount0, inAmount1, p.MustCalcPrice1To0Center()) if autoswap { residualAmount0 := maxAmount0.Sub(inAmount0) @@ -138,14 +138,15 @@ func (p *Pool) Deposit( // NOTE: Currently not doing anything with the error, // but added error handling to all of the new functions for autoswap. // Open to changing it however. - residualShares, _ := p.CalcResidualSharesMinted(residualAmount0, residualAmount1) + residualValue, _ := p.CalcResidualValue(residualAmount0, residualAmount1) - outShares = outShares.Add(residualShares) + depositValueAsToken0 = depositValueAsToken0.Add(residualValue) inAmount0 = maxAmount0 inAmount1 = maxAmount1 } + outShares = p.CalcSharesMinted(depositValueAsToken0, existingShares) *lowerReserve0 = lowerReserve0.Add(inAmount0) *upperReserve1 = upperReserve1.Add(inAmount1) @@ -171,12 +172,10 @@ func (p *Pool) MustCalcPrice1To0Center() math_utils.PrecDec { } func (p *Pool) CalcSharesMinted( - amount0 math.Int, - amount1 math.Int, + depositValueAsToken0 math_utils.PrecDec, existingShares math.Int, ) (sharesMinted sdk.Coin) { price1To0Center := p.MustCalcPrice1To0Center() - valueMintedToken0 := CalcAmountAsToken0(amount0, amount1, price1To0Center) valueExistingToken0 := CalcAmountAsToken0( p.LowerTick0.ReservesMakerDenom, @@ -185,34 +184,16 @@ func (p *Pool) CalcSharesMinted( ) var sharesMintedAmount math.Int if valueExistingToken0.GT(math_utils.ZeroPrecDec()) { - sharesMintedAmount = valueMintedToken0.MulInt(existingShares). + sharesMintedAmount = depositValueAsToken0.MulInt(existingShares). Quo(valueExistingToken0). TruncateInt() } else { - sharesMintedAmount = valueMintedToken0.TruncateInt() + sharesMintedAmount = depositValueAsToken0.TruncateInt() } return sdk.Coin{Denom: p.GetPoolDenom(), Amount: sharesMintedAmount} } -func (p *Pool) CalcResidualSharesMinted( - residualAmount0 math.Int, - residualAmount1 math.Int, -) (sharesMinted sdk.Coin, err error) { - fee := p.Fee() - valueMintedToken0, err := CalcResidualValue( - residualAmount0, - residualAmount1, - p.LowerTick0.PriceTakerToMaker, - fee, - ) - if err != nil { - return sdk.Coin{Denom: p.GetPoolDenom()}, err - } - - return sdk.Coin{Denom: p.GetPoolDenom(), Amount: valueMintedToken0.TruncateInt()}, nil -} - func (p *Pool) RedeemValue(sharesToRemove, totalShares math.Int) (outAmount0, outAmount1 math.Int) { reserves0 := &p.LowerTick0.ReservesMakerDenom reserves1 := &p.UpperTick1.ReservesMakerDenom @@ -267,18 +248,16 @@ func CalcGreatestMatchingRatio( return resultAmount0, resultAmount1 } -func CalcResidualValue( +func (p *Pool) CalcResidualValue( amount0, amount1 math.Int, - priceLowerTakerToMaker math_utils.PrecDec, - fee uint64, ) (math_utils.PrecDec, error) { - feeInt64 := utils.MustSafeUint64ToInt64(fee) + feeInt64 := utils.MustSafeUint64ToInt64(p.Fee()) amount0Discount, err := CalcPrice(feeInt64) if err != nil { return math_utils.ZeroPrecDec(), err } // ResidualValue = Amount0 * (Price1to0Center / Price1to0Upper) + Amount1 * Price1to0Lower - return amount0Discount.MulInt(amount0).Add(priceLowerTakerToMaker.MulInt(amount1)), nil + return amount0Discount.MulInt(amount0).Add(p.LowerTick0.PriceTakerToMaker.MulInt(amount1)), nil } func CalcAmountAsToken0(amount0, amount1 math.Int, price1To0 math_utils.PrecDec) math_utils.PrecDec { From 617ef9d33de7bd97d07422f7c92773fe8cd577ec Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Thu, 15 Aug 2024 02:01:42 -0400 Subject: [PATCH 05/21] state test things --- tests/dex/state_test.go | 327 +++++++++++++++++++++++++++++++++------- 1 file changed, 275 insertions(+), 52 deletions(-) diff --git a/tests/dex/state_test.go b/tests/dex/state_test.go index 1189b4a4c..986793ec0 100644 --- a/tests/dex/state_test.go +++ b/tests/dex/state_test.go @@ -2,15 +2,16 @@ package dex_state_test import ( "fmt" - "reflect" "testing" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/neutron-org/neutron/v4/testutil/apptesting" "github.com/neutron-org/neutron/v4/testutil/common/sample" + math_utils "github.com/neutron-org/neutron/v4/utils/math" dexkeeper "github.com/neutron-org/neutron/v4/x/dex/keeper" dextypes "github.com/neutron-org/neutron/v4/x/dex/types" + "github.com/stretchr/testify/require" ) // Shared Setup Code ////////////////////////////////////////////////////////// @@ -21,6 +22,13 @@ const ( False = "False" ) +// Percents +const ( + ZeroPCT string = "0" + FiftyPCT = "50" + HundredPct = "100" +) + // ExistingShareHolders const ( None string = "None" @@ -31,7 +39,8 @@ const ( // LiquidityDistribution const ( - TokenA0TokenB1 string = "TokenA0TokenB1" + TokenA0TokenB0 string = "TokenA0TokenB0" + TokenA0TokenB1 = "TokenA0TokenB1" TokenA0TokenB2 = "TokenA0TokenB2" TokenA1TokenB0 = "TokenA1TokenB0" TokenA1TokenB1 = "TokenA1TokenB1" @@ -59,24 +68,61 @@ type LiquidityDistribution struct { TokenB sdk.Coin } +func (l LiquidityDistribution) doubleSided() bool { + return l.TokenA.Amount.IsPositive() && l.TokenB.Amount.IsPositive() +} + +func (l LiquidityDistribution) empty() bool { + return l.TokenA.Amount.IsZero() && l.TokenB.Amount.IsZero() +} + +func (l LiquidityDistribution) singleSided() bool { + return !l.doubleSided() && !l.empty() +} + +func (l LiquidityDistribution) hasTokenA() bool { + return l.TokenA.Amount.IsPositive() +} + +func (l LiquidityDistribution) hasTokenB() bool { + return l.TokenB.Amount.IsPositive() +} + +type SharedParams struct { + Tick int64 + Fee uint64 + PairID dextypes.PairID +} + +var DefaultSharedParams SharedParams = SharedParams{ + Tick: DefaultTick, + Fee: DefaultFee, +} + +func (s *DexStateTestSuite) intsEqual(field string, expected, actual math.Int) { + s.True(actual.Equal(expected), "For %v: Expected %v Got %v", field, expected, actual) +} + func parseLiquidityDistribution(liquidityDistribution string) LiquidityDistribution { switch liquidityDistribution { + case TokenA0TokenB0: + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.ZeroInt()), TokenB: sdk.NewCoin("TokenB", math.ZeroInt())} case TokenA0TokenB1: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(0).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(1)).Mul(BaseTokenAmountInt)} + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.ZeroInt()), TokenB: sdk.NewCoin("TokenB", math.NewInt(1).Mul(BaseTokenAmountInt))} case TokenA0TokenB2: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(0).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(2)).Mul(BaseTokenAmountInt)} + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.ZeroInt()), TokenB: sdk.NewCoin("TokenB", math.NewInt(2).Mul(BaseTokenAmountInt))} case TokenA1TokenB0: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(0)).Mul(BaseTokenAmountInt)} + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.ZeroInt())} case TokenA1TokenB1: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(1)).Mul(BaseTokenAmountInt)} + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(1).Mul(BaseTokenAmountInt))} case TokenA1TokenB2: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(2)).Mul(BaseTokenAmountInt)} + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(2).Mul(BaseTokenAmountInt))} case TokenA2TokenB0: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(2).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(0)).Mul(BaseTokenAmountInt)} + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(2).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.ZeroInt())} case TokenA2TokenB1: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(2).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(1)).Mul(BaseTokenAmountInt)} + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(2).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(1).Mul(BaseTokenAmountInt))} case TokenA2TokenB2: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(2).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(2)).Mul(BaseTokenAmountInt)} + return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(2).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(2).Mul(BaseTokenAmountInt))} default: panic("invalid liquidity distribution") } @@ -113,11 +159,22 @@ func splitLiquidityDistribution(liquidityDistribution LiquidityDistribution, n i } -func generatePermutations[T any](stateType T, testStates []testParams) []T { - result := make([]T, 0) +func cloneMap(original map[string]string) map[string]string { + // Create a new map + cloned := make(map[string]string) + // Copy each key-value pair from the original map to the new map + for key, value := range original { + cloned[key] = value + } - var generate func(index int, current T) - generate = func(index int, current T) { + return cloned +} + +func generatePermutations(testStates []testParams) []map[string]string { + result := make([]map[string]string, 0) + + var generate func(int, map[string]string) + generate = func(index int, current map[string]string) { // Base Case if index == len(testStates) { @@ -128,16 +185,15 @@ func generatePermutations[T any](stateType T, testStates []testParams) []T { // Iterate through all possible values and create new states for _, value := range testStates[index].states { fieldName := testStates[index].field - temp := current - v := reflect.ValueOf(&temp).Elem() - field := v.FieldByName(fieldName) - field.SetString(value) + temp := cloneMap(current) + temp[fieldName] = value generate(index+1, temp) } } + emptyMap := make(map[string]string) + generate(0, emptyMap) - generate(0, stateType) return result } @@ -173,9 +229,8 @@ type DexStateTestSuite struct { alice sdk.AccAddress } -func (s *DexStateTestSuite) SetupTest() { +func (s *DexStateTestSuite) SetupTest(t *testing.T) { s.Setup() - s.creator = sdk.MustAccAddressFromBech32(sample.AccAddress()) s.alice = sdk.MustAccAddressFromBech32(sample.AccAddress()) @@ -184,17 +239,19 @@ func (s *DexStateTestSuite) SetupTest() { // Deposit State Test ///////////////////////////////////////////////////////// type depositTestParams struct { + SharedParams // State Conditions ExistingShareHolders string - LiquidityDistribution string + LiquidityDistribution LiquidityDistribution + PoolValueIncrease LiquidityDistribution // Message Variants - DisableAutoswap string - FailTxOnBEL string - DepositAmounts string + DisableAutoswap bool + FailTxOnBEL bool + DepositAmounts LiquidityDistribution } func (s *DexStateTestSuite) setupDepositState(params depositTestParams) { - liquidityDistr := parseLiquidityDistribution(params.LiquidityDistribution) + liquidityDistr := params.LiquidityDistribution switch params.ExistingShareHolders { case None: @@ -208,15 +265,34 @@ func (s *DexStateTestSuite) setupDepositState(params depositTestParams) { s.makeDepositSuccess(s.creator, liqDistrArr[1], false) s.makeDepositSuccess(s.alice, liqDistrArr[0], false) } + + // handle pool value increase + + if !params.PoolValueIncrease.empty() { + // Increase the value of the pool. This is analogous to a pool being swapped through + pool, found := s.App.DexKeeper.GetPool(s.Ctx, ¶ms.PairID, params.Tick, params.Fee) + s.True(found, "Pool not found") + + pool.LowerTick0.ReservesMakerDenom = pool.LowerTick0.ReservesMakerDenom.Add(params.PoolValueIncrease.TokenA.Amount) + pool.UpperTick1.ReservesMakerDenom = pool.UpperTick1.ReservesMakerDenom.Add(params.PoolValueIncrease.TokenB.Amount) + s.App.DexKeeper.SetPool(s.Ctx, pool) + } +} + +func CalcTotalPreDepositLiquidity(params depositTestParams) LiquidityDistribution { + return LiquidityDistribution{ + TokenA: params.LiquidityDistribution.TokenA.Add(params.PoolValueIncrease.TokenA), + TokenB: params.LiquidityDistribution.TokenB.Add(params.PoolValueIncrease.TokenB), + } } -func CalcDepositOutput( - existingDistr, depositDistr LiquidityDistribution, -) (resultAmountA, resultAmountB math.Int) { - depositA := depositDistr.TokenA.Amount - depositB := depositDistr.TokenB.Amount - existingA := existingDistr.TokenA.Amount - existingB := existingDistr.TokenB.Amount +func CalcDepositOutput(params depositTestParams) (resultAmountA, resultAmountB math.Int) { + depositA := params.DepositAmounts.TokenA.Amount + depositB := params.DepositAmounts.TokenB.Amount + + existingLiquidity := CalcTotalPreDepositLiquidity(params) + existingA := existingLiquidity.TokenA.Amount + existingB := existingLiquidity.TokenB.Amount switch { //Pool is empty can deposit full amounts @@ -231,10 +307,10 @@ func CalcDepositOutput( // Pool has a ratio of A and B, deposit must match this ratio case existingA.IsPositive() && existingB.IsPositive(): targetRatioA := math.LegacyNewDecFromInt(existingA).Quo(math.LegacyNewDecFromInt(existingB)) - maxAmountA := math.LegacyNewDecFromInt(depositA).Mul(targetRatioA).TruncateInt() + maxAmountA := math.LegacyNewDecFromInt(depositB).Mul(targetRatioA).TruncateInt() resultAmountA = math.MinInt(depositA, maxAmountA) targetRatioB := math.LegacyOneDec().Quo(targetRatioA) - maxAmountB := math.LegacyNewDecFromInt(depositB).Mul(targetRatioB).TruncateInt() + maxAmountB := math.LegacyNewDecFromInt(depositA).Mul(targetRatioB).TruncateInt() resultAmountB = math.MinInt(depositB, maxAmountB) return resultAmountA, resultAmountB @@ -243,22 +319,144 @@ func CalcDepositOutput( } } -func calcExpectedDepositAmounts(existingDistr, depositDistr LiquidityDistribution, disableAutoSwap bool) (tokenAAmount, tokenBAmount math.Int) { +func calcDepositValueAsToken0(tick int64, amount0, amount1 math.Int) math_utils.PrecDec { + price1To0CenterTick := dextypes.MustCalcPrice(tick) + amount1ValueAsToken0 := price1To0CenterTick.MulInt(amount1) + depositValue := amount1ValueAsToken0.Add(math_utils.NewPrecDecFromInt(amount0)) + + return depositValue + +} + +func calcCurrentShareValue(params depositTestParams) math_utils.PrecDec { + initialValueA := params.LiquidityDistribution.TokenA.Amount + initialValueB := params.LiquidityDistribution.TokenB.Amount + + existingShares := calcDepositValueAsToken0(params.Tick, initialValueA, initialValueB).TruncateInt() + if existingShares.IsZero() { + return math_utils.OnePrecDec() + } + + totalValueA := initialValueA.Add(params.PoolValueIncrease.TokenA.Amount) + totalValueB := initialValueB.Add(params.PoolValueIncrease.TokenB.Amount) + + totalPreDepositValue := calcDepositValueAsToken0(params.Tick, totalValueA, totalValueB) + currentShareValue := math_utils.NewPrecDecFromInt(existingShares).Quo(totalPreDepositValue) + + return currentShareValue +} + +func calcDepositValue(params depositTestParams, depositAmount0, depositAmount1 math.Int) math_utils.PrecDec { + rawValueDeposit := calcDepositValueAsToken0(params.Tick, depositAmount0, depositAmount1) + + return rawValueDeposit +} + +func calcAutoSwapResidualValue(params depositTestParams, residual0, residual1 math.Int) math_utils.PrecDec { + swapFeeDeduction := dextypes.MustCalcPrice(int64(params.Fee)) + + switch { + // We must autoswap TokenA + case residual0.IsPositive() && residual1.IsPositive(): + panic("residual0 and residual1 cannot both be positive") + case residual0.IsPositive(): + return swapFeeDeduction.MulInt(residual0) + case residual1.IsPositive(): + price1To0CenterTick := dextypes.MustCalcPrice(params.Tick) + token1AsToken0 := price1To0CenterTick.MulInt(residual1) + return swapFeeDeduction.Mul(token1AsToken0) + default: + panic("residual0 and residual1 cannot both be zero") + + } +} + +func calcExpectedDepositAmounts(params depositTestParams) (tokenAAmount, tokenBAmount, sharesIssued math.Int) { + + amountAWithoutAutoswap, amountBWithoutAutoswap := CalcDepositOutput(params) + + sharesIssuedWithoutAutoswap := calcDepositValue(params, amountAWithoutAutoswap, amountBWithoutAutoswap) - amountAWithoutAutoswap, amountBWithoutAutoswap := CalcDepositOutput(existingDistr, depositDistr) + residualA := params.DepositAmounts.TokenA.Amount.Sub(amountAWithoutAutoswap) + residualB := params.DepositAmounts.TokenB.Amount.Sub(amountBWithoutAutoswap) - if disableAutoSwap { - return amountAWithoutAutoswap, amountBWithoutAutoswap + autoswapSharesIssued := math_utils.ZeroPrecDec() + if !params.DisableAutoswap && (residualA.IsPositive() || residualB.IsPositive()) { + autoswapSharesIssued = calcAutoSwapResidualValue(params, residualA, residualB) + tokenAAmount = params.DepositAmounts.TokenA.Amount + tokenBAmount = params.DepositAmounts.TokenB.Amount + } else { + tokenAAmount = amountAWithoutAutoswap + tokenBAmount = amountBWithoutAutoswap } + totalDepositValue := autoswapSharesIssued.Add(sharesIssuedWithoutAutoswap) + currentShareValue := calcCurrentShareValue(params) + sharesIssued = totalDepositValue.Mul(currentShareValue).TruncateInt() + + return tokenAAmount, tokenBAmount, sharesIssued +} + +func (s *DexStateTestSuite) handleBaseFailureCases(params depositTestParams, err error) { + currentLiquidity := CalcTotalPreDepositLiquidity(params) + // cannot deposit single sided liquidity into a non-empty pool if you are missing one of the tokens in the pool + if !currentLiquidity.empty() { + if (!params.DepositAmounts.hasTokenA() && currentLiquidity.hasTokenA()) || (!params.DepositAmounts.hasTokenB() && currentLiquidity.hasTokenB()) { + s.ErrorIs(err, dextypes.ErrZeroTrueDeposit) + s.T().Skip("Ending test due to expected error") + } + } } -func (s *DexStateTestSuite) validateDepositResult(params depositTestParams, _ *dextypes.MsgDepositResponse, err error) { - // Handle case where disableAutoswap == true and deposit is imbalanced - if params.DisableAutoswap == True && params.DepositAmounts != params.LiquidityDistribution { - s.ErrorIs(err, dextypes.ErrZeroTrueDeposit) +func HydrateDepositTestCase(params map[string]string) depositTestParams { + existingShareHolders := params["ExistingShareHolders"] + var liquidityDistribution LiquidityDistribution + + if existingShareHolders == None { + liquidityDistribution = parseLiquidityDistribution(TokenA0TokenB0) + } else { + liquidityDistribution = parseLiquidityDistribution(params["LiquidityDistribution"]) } + var valueIncrease LiquidityDistribution + if liquidityDistribution.empty() { + valueIncrease = parseLiquidityDistribution(TokenA0TokenB0) + } else { + valueIncrease = parseLiquidityDistribution(params["PoolValueIncrease"]) + } + + sharedParams := DefaultSharedParams + sharedParams.PairID = dextypes.PairID{Token0: "TokenA", Token1: "TokenB"} + + return depositTestParams{ + ExistingShareHolders: existingShareHolders, + LiquidityDistribution: liquidityDistribution, + DisableAutoswap: parseBool(params["DisableAutoswap"]), + PoolValueIncrease: valueIncrease, + DepositAmounts: parseLiquidityDistribution(params["DepositAmounts"]), + SharedParams: sharedParams, + } +} + +func HydrateAllDepositTestCases(paramsList []map[string]string) []depositTestParams { + allTCs := make([]depositTestParams, 0) + for _, paramsRaw := range paramsList { + allTCs = append(allTCs, HydrateDepositTestCase(paramsRaw)) + } + + result := make([]depositTestParams, 0) + + // De-dupe test cases hydration creates some duplicates + seenTCs := make(map[string]bool) + for _, tc := range allTCs { + tcStr := fmt.Sprintf("%v", tc) + if _, ok := seenTCs[tcStr]; !ok { + result = append(result, tc) + } + seenTCs[tcStr] = true + } + + return result } func TestDeposit(t *testing.T) { @@ -275,7 +473,8 @@ func TestDeposit(t *testing.T) { TokenA2TokenB2, }}, {field: "DisableAutoswap", states: []string{True, False}}, - // {field: "FailTxOnBEL", states: []string{True, False}}, I don't think this needs to be tested + {field: "PoolValueIncrease", states: []string{TokenA0TokenB0, TokenA1TokenB0, TokenA0TokenB1}}, + // {field: "FailTxOnBEL", states: []string{True, False}}, // I don't think this needs to be tested {field: "DepositAmounts", states: []string{ TokenA0TokenB1, TokenA0TokenB2, @@ -284,25 +483,49 @@ func TestDeposit(t *testing.T) { TokenA2TokenB2, }}, } - testCases := generatePermutations(depositTestParams{}, testParams) + testCasesRaw := generatePermutations(testParams) + testCases := HydrateAllDepositTestCases(testCasesRaw) for _, tc := range testCases { testName := fmt.Sprintf("%v", tc) t.Run(testName, func(t *testing.T) { s := new(DexStateTestSuite) + s.SetT(t) + // TODO: we don't want to rebuild the app for every test. Instead we should just use new pools + s.SetupTest(t) + s.setupDepositState(tc) - disableAutoSwap := parseBool(tc.DisableAutoswap) - depositAmts := parseLiquidityDistribution(tc.DepositAmounts) - pairID := dextypes.PairID{Token0: "TokenA", Token1: "TokenB"} - poolID, found := s.App.DexKeeper.GetPoolIDByParams(s.Ctx, &pairID, DefaultTick, DefaultFee) - s.True(found, "Pool not found after deposit") + poolID, found := s.App.DexKeeper.GetPoolIDByParams(s.Ctx, &tc.PairID, tc.Tick, tc.Fee) + + if tc.ExistingShareHolders == None { + poolID = 0 + } else { + require.True(t, found, "Pool not found after deposit") + } + poolDenom := dextypes.NewPoolDenom(poolID) existingSharesOwned := s.App.BankKeeper.GetBalance(s.Ctx, s.creator, poolDenom) - resp, err := s.makeDeposit(s.creator, depositAmts, disableAutoSwap) - s.validateDepositResult(tc, resp, err) + // Do the actual deposit + resp, err := s.makeDeposit(s.creator, tc.DepositAmounts, tc.DisableAutoswap) + + s.handleBaseFailureCases(tc, err) + s.NoError(err) + + expectedDepositA, expectedDepositB, expectedShares := calcExpectedDepositAmounts(tc) + + //Check that response is correct + s.intsEqual("Response Deposit0", expectedDepositA, resp.Reserve0Deposited[0]) + s.intsEqual("Response Deposit1", expectedDepositB, resp.Reserve1Deposited[0]) + + newSharesOwned := s.App.BankKeeper.GetBalance(s.Ctx, s.creator, poolDenom) + sharesIssued := newSharesOwned.Sub(existingSharesOwned) + s.intsEqual("Shares Issued", expectedShares, sharesIssued.Amount) + + // TODO: balance checks for tokens + // TODO: maybe check actual dex state }) } From 189837705585a863b9347ee0aaf354570f2f5ef5 Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Mon, 19 Aug 2024 13:28:18 -0400 Subject: [PATCH 06/21] refactor tests --- .../{state_test.go => state_deposit_test.go} | 288 ++---------------- tests/dex/state_setup_test.go | 276 +++++++++++++++++ 2 files changed, 297 insertions(+), 267 deletions(-) rename tests/dex/{state_test.go => state_deposit_test.go} (52%) create mode 100644 tests/dex/state_setup_test.go diff --git a/tests/dex/state_test.go b/tests/dex/state_deposit_test.go similarity index 52% rename from tests/dex/state_test.go rename to tests/dex/state_deposit_test.go index 986793ec0..efa841b08 100644 --- a/tests/dex/state_test.go +++ b/tests/dex/state_deposit_test.go @@ -5,239 +5,11 @@ import ( "testing" "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/neutron-org/neutron/v4/testutil/apptesting" - "github.com/neutron-org/neutron/v4/testutil/common/sample" math_utils "github.com/neutron-org/neutron/v4/utils/math" - dexkeeper "github.com/neutron-org/neutron/v4/x/dex/keeper" dextypes "github.com/neutron-org/neutron/v4/x/dex/types" "github.com/stretchr/testify/require" ) -// Shared Setup Code ////////////////////////////////////////////////////////// - -// Bools -const ( - True string = "True" - False = "False" -) - -// Percents -const ( - ZeroPCT string = "0" - FiftyPCT = "50" - HundredPct = "100" -) - -// ExistingShareHolders -const ( - None string = "None" - Creator = "Creator" - OneOther = "OneOther" - OneOtherAndCreator = "OneOtherAndCreator" -) - -// LiquidityDistribution -const ( - TokenA0TokenB0 string = "TokenA0TokenB0" - TokenA0TokenB1 = "TokenA0TokenB1" - TokenA0TokenB2 = "TokenA0TokenB2" - TokenA1TokenB0 = "TokenA1TokenB0" - TokenA1TokenB1 = "TokenA1TokenB1" - TokenA1TokenB2 = "TokenA1TokenB2" - TokenA2TokenB0 = "TokenA2TokenB0" - TokenA2TokenB1 = "TokenA2TokenB1" - TokenA2TokenB2 = "TokenA2TokenB2" -) - -const ( - BaseTokenAmount = 1_000_000 - DefaultTick = 0 - DefaultFee = 1 -) - -var BaseTokenAmountInt = math.NewInt(BaseTokenAmount) - -type testParams struct { - field string - states []string -} - -type LiquidityDistribution struct { - TokenA sdk.Coin - TokenB sdk.Coin -} - -func (l LiquidityDistribution) doubleSided() bool { - return l.TokenA.Amount.IsPositive() && l.TokenB.Amount.IsPositive() -} - -func (l LiquidityDistribution) empty() bool { - return l.TokenA.Amount.IsZero() && l.TokenB.Amount.IsZero() -} - -func (l LiquidityDistribution) singleSided() bool { - return !l.doubleSided() && !l.empty() -} - -func (l LiquidityDistribution) hasTokenA() bool { - return l.TokenA.Amount.IsPositive() -} - -func (l LiquidityDistribution) hasTokenB() bool { - return l.TokenB.Amount.IsPositive() -} - -type SharedParams struct { - Tick int64 - Fee uint64 - PairID dextypes.PairID -} - -var DefaultSharedParams SharedParams = SharedParams{ - Tick: DefaultTick, - Fee: DefaultFee, -} - -func (s *DexStateTestSuite) intsEqual(field string, expected, actual math.Int) { - s.True(actual.Equal(expected), "For %v: Expected %v Got %v", field, expected, actual) -} - -func parseLiquidityDistribution(liquidityDistribution string) LiquidityDistribution { - switch liquidityDistribution { - case TokenA0TokenB0: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.ZeroInt()), TokenB: sdk.NewCoin("TokenB", math.ZeroInt())} - case TokenA0TokenB1: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.ZeroInt()), TokenB: sdk.NewCoin("TokenB", math.NewInt(1).Mul(BaseTokenAmountInt))} - case TokenA0TokenB2: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.ZeroInt()), TokenB: sdk.NewCoin("TokenB", math.NewInt(2).Mul(BaseTokenAmountInt))} - case TokenA1TokenB0: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.ZeroInt())} - case TokenA1TokenB1: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(1).Mul(BaseTokenAmountInt))} - case TokenA1TokenB2: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(2).Mul(BaseTokenAmountInt))} - case TokenA2TokenB0: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(2).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.ZeroInt())} - case TokenA2TokenB1: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(2).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(1).Mul(BaseTokenAmountInt))} - case TokenA2TokenB2: - return LiquidityDistribution{TokenA: sdk.NewCoin("TokenA", math.NewInt(2).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin("TokenB", math.NewInt(2).Mul(BaseTokenAmountInt))} - default: - panic("invalid liquidity distribution") - } -} - -func parseBool(b string) bool { - switch b { - case True: - return true - case False: - return false - default: - panic("invalid bool") - - } -} - -func splitLiquidityDistribution(liquidityDistribution LiquidityDistribution, n int64) []LiquidityDistribution { - nInt := math.NewInt(n) - amount0 := liquidityDistribution.TokenA.Amount.Quo(nInt) - amount1 := liquidityDistribution.TokenB.Amount.Quo(nInt) - - result := make([]LiquidityDistribution, n) - for i := range n { - - result[i] = LiquidityDistribution{ - TokenA: sdk.NewCoin(liquidityDistribution.TokenA.Denom, amount0), - TokenB: sdk.NewCoin(liquidityDistribution.TokenB.Denom, amount1), - } - - } - - return result - -} - -func cloneMap(original map[string]string) map[string]string { - // Create a new map - cloned := make(map[string]string) - // Copy each key-value pair from the original map to the new map - for key, value := range original { - cloned[key] = value - } - - return cloned -} - -func generatePermutations(testStates []testParams) []map[string]string { - result := make([]map[string]string, 0) - - var generate func(int, map[string]string) - generate = func(index int, current map[string]string) { - - // Base Case - if index == len(testStates) { - result = append(result, current) - return - } - - // Iterate through all possible values and create new states - for _, value := range testStates[index].states { - fieldName := testStates[index].field - temp := cloneMap(current) - temp[fieldName] = value - generate(index+1, temp) - } - - } - emptyMap := make(map[string]string) - generate(0, emptyMap) - - return result -} - -func (s *DexStateTestSuite) makeDeposit(addr sdk.AccAddress, depositAmts LiquidityDistribution, disableAutoSwap bool) (*dextypes.MsgDepositResponse, error) { - coins := sdk.NewCoins(depositAmts.TokenA, depositAmts.TokenB) - s.FundAcc(addr, coins) - - return s.msgServer.Deposit(s.Ctx, &dextypes.MsgDeposit{ - - Creator: addr.String(), - Receiver: addr.String(), - TokenA: depositAmts.TokenA.Denom, - TokenB: depositAmts.TokenB.Denom, - AmountsA: []math.Int{depositAmts.TokenA.Amount}, - AmountsB: []math.Int{depositAmts.TokenB.Amount}, - TickIndexesAToB: []int64{DefaultTick}, - Fees: []uint64{DefaultFee}, - Options: []*dextypes.DepositOptions{{DisableAutoswap: disableAutoSwap}}, - }) -} - -func (s *DexStateTestSuite) makeDepositSuccess(addr sdk.AccAddress, depositAmts LiquidityDistribution, disableAutoSwap bool) *dextypes.MsgDepositResponse { - resp, err := s.makeDeposit(addr, depositAmts, disableAutoSwap) - s.NoError(err) - - return resp -} - -type DexStateTestSuite struct { - apptesting.KeeperTestHelper - msgServer dextypes.MsgServer - creator sdk.AccAddress - alice sdk.AccAddress -} - -func (s *DexStateTestSuite) SetupTest(t *testing.T) { - s.Setup() - s.creator = sdk.MustAccAddressFromBech32(sample.AccAddress()) - s.alice = sdk.MustAccAddressFromBech32(sample.AccAddress()) - - s.msgServer = dexkeeper.NewMsgServerImpl(s.App.DexKeeper) -} - -// Deposit State Test ///////////////////////////////////////////////////////// type depositTestParams struct { SharedParams // State Conditions @@ -270,7 +42,7 @@ func (s *DexStateTestSuite) setupDepositState(params depositTestParams) { if !params.PoolValueIncrease.empty() { // Increase the value of the pool. This is analogous to a pool being swapped through - pool, found := s.App.DexKeeper.GetPool(s.Ctx, ¶ms.PairID, params.Tick, params.Fee) + pool, found := s.App.DexKeeper.GetPool(s.Ctx, params.PairID, params.Tick, params.Fee) s.True(found, "Pool not found") pool.LowerTick0.ReservesMakerDenom = pool.LowerTick0.ReservesMakerDenom.Add(params.PoolValueIncrease.TokenA.Amount) @@ -319,15 +91,6 @@ func CalcDepositOutput(params depositTestParams) (resultAmountA, resultAmountB m } } -func calcDepositValueAsToken0(tick int64, amount0, amount1 math.Int) math_utils.PrecDec { - price1To0CenterTick := dextypes.MustCalcPrice(tick) - amount1ValueAsToken0 := price1To0CenterTick.MulInt(amount1) - depositValue := amount1ValueAsToken0.Add(math_utils.NewPrecDecFromInt(amount0)) - - return depositValue - -} - func calcCurrentShareValue(params depositTestParams) math_utils.PrecDec { initialValueA := params.LiquidityDistribution.TokenA.Amount initialValueB := params.LiquidityDistribution.TokenB.Amount @@ -408,55 +171,44 @@ func (s *DexStateTestSuite) handleBaseFailureCases(params depositTestParams, err } } -func HydrateDepositTestCase(params map[string]string) depositTestParams { +func HydrateDepositTestCase(params map[string]string, pairID *dextypes.PairID) depositTestParams { existingShareHolders := params["ExistingShareHolders"] var liquidityDistribution LiquidityDistribution if existingShareHolders == None { - liquidityDistribution = parseLiquidityDistribution(TokenA0TokenB0) + liquidityDistribution = parseLiquidityDistribution(TokenA0TokenB0, pairID) } else { - liquidityDistribution = parseLiquidityDistribution(params["LiquidityDistribution"]) + liquidityDistribution = parseLiquidityDistribution(params["LiquidityDistribution"], pairID) } var valueIncrease LiquidityDistribution if liquidityDistribution.empty() { - valueIncrease = parseLiquidityDistribution(TokenA0TokenB0) + valueIncrease = parseLiquidityDistribution(TokenA0TokenB0, pairID) } else { - valueIncrease = parseLiquidityDistribution(params["PoolValueIncrease"]) + valueIncrease = parseLiquidityDistribution(params["PoolValueIncrease"], pairID) } - sharedParams := DefaultSharedParams - sharedParams.PairID = dextypes.PairID{Token0: "TokenA", Token1: "TokenB"} - return depositTestParams{ ExistingShareHolders: existingShareHolders, LiquidityDistribution: liquidityDistribution, DisableAutoswap: parseBool(params["DisableAutoswap"]), PoolValueIncrease: valueIncrease, - DepositAmounts: parseLiquidityDistribution(params["DepositAmounts"]), - SharedParams: sharedParams, + DepositAmounts: parseLiquidityDistribution(params["DepositAmounts"], pairID), + SharedParams: DefaultSharedParams, } } func HydrateAllDepositTestCases(paramsList []map[string]string) []depositTestParams { allTCs := make([]depositTestParams, 0) - for _, paramsRaw := range paramsList { - allTCs = append(allTCs, HydrateDepositTestCase(paramsRaw)) + for i, paramsRaw := range paramsList { + pairID := generatePairID(i) + tc := HydrateDepositTestCase(paramsRaw, pairID) + tc.PairID = pairID + allTCs = append(allTCs, tc) } - result := make([]depositTestParams, 0) - // De-dupe test cases hydration creates some duplicates - seenTCs := make(map[string]bool) - for _, tc := range allTCs { - tcStr := fmt.Sprintf("%v", tc) - if _, ok := seenTCs[tcStr]; !ok { - result = append(result, tc) - } - seenTCs[tcStr] = true - } - - return result + return removeDuplicateTests(allTCs) } func TestDeposit(t *testing.T) { @@ -486,20 +238,22 @@ func TestDeposit(t *testing.T) { testCasesRaw := generatePermutations(testParams) testCases := HydrateAllDepositTestCases(testCasesRaw) + s := new(DexStateTestSuite) + s.SetT(t) + s.SetupTest(t) + for _, tc := range testCases { testName := fmt.Sprintf("%v", tc) t.Run(testName, func(t *testing.T) { - s := new(DexStateTestSuite) s.SetT(t) - // TODO: we don't want to rebuild the app for every test. Instead we should just use new pools - s.SetupTest(t) s.setupDepositState(tc) - poolID, found := s.App.DexKeeper.GetPoolIDByParams(s.Ctx, &tc.PairID, tc.Tick, tc.Fee) + poolID, found := s.App.DexKeeper.GetPoolIDByParams(s.Ctx, tc.PairID, tc.Tick, tc.Fee) if tc.ExistingShareHolders == None { - poolID = 0 + // This is the ID that will be used when the pool is created + poolID = s.App.DexKeeper.GetPoolCount(s.Ctx) } else { require.True(t, found, "Pool not found after deposit") } diff --git a/tests/dex/state_setup_test.go b/tests/dex/state_setup_test.go new file mode 100644 index 000000000..3a9f3aacf --- /dev/null +++ b/tests/dex/state_setup_test.go @@ -0,0 +1,276 @@ +package dex_state_test + +import ( + "fmt" + "testing" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/neutron-org/neutron/v4/testutil/apptesting" + "github.com/neutron-org/neutron/v4/testutil/common/sample" + math_utils "github.com/neutron-org/neutron/v4/utils/math" + dexkeeper "github.com/neutron-org/neutron/v4/x/dex/keeper" + dextypes "github.com/neutron-org/neutron/v4/x/dex/types" +) + +// Constants ////////////////////////////////////////////////////////////////// + +// Bools +const ( + True string = "True" + False = "False" +) + +// Percents +const ( + ZeroPCT string = "0" + FiftyPCT = "50" + HundredPct = "100" +) + +// ExistingShareHolders +const ( + None string = "None" + Creator = "Creator" + OneOther = "OneOther" + OneOtherAndCreator = "OneOtherAndCreator" +) + +// LiquidityDistribution +const ( + TokenA0TokenB0 string = "TokenA0TokenB0" + TokenA0TokenB1 = "TokenA0TokenB1" + TokenA0TokenB2 = "TokenA0TokenB2" + TokenA1TokenB0 = "TokenA1TokenB0" + TokenA1TokenB1 = "TokenA1TokenB1" + TokenA1TokenB2 = "TokenA1TokenB2" + TokenA2TokenB0 = "TokenA2TokenB0" + TokenA2TokenB1 = "TokenA2TokenB1" + TokenA2TokenB2 = "TokenA2TokenB2" +) + +// Default Values +const ( + BaseTokenAmount = 1_000_000 + DefaultTick = 0 + DefaultFee = 1 +) + +var BaseTokenAmountInt = math.NewInt(BaseTokenAmount) + +type SharedParams struct { + Tick int64 + Fee uint64 + PairID *dextypes.PairID +} + +var DefaultSharedParams SharedParams = SharedParams{ + Tick: DefaultTick, + Fee: DefaultFee, +} + +// Types ////////////////////////////////////////////////////////////////////// + +type LiquidityDistribution struct { + TokenA sdk.Coin + TokenB sdk.Coin +} + +func (l LiquidityDistribution) doubleSided() bool { + return l.TokenA.Amount.IsPositive() && l.TokenB.Amount.IsPositive() +} + +func (l LiquidityDistribution) empty() bool { + return l.TokenA.Amount.IsZero() && l.TokenB.Amount.IsZero() +} + +func (l LiquidityDistribution) singleSided() bool { + return !l.doubleSided() && !l.empty() +} + +func (l LiquidityDistribution) hasTokenA() bool { + return l.TokenA.Amount.IsPositive() +} + +func (l LiquidityDistribution) hasTokenB() bool { + return l.TokenB.Amount.IsPositive() +} + +func splitLiquidityDistribution(liquidityDistribution LiquidityDistribution, n int64) []LiquidityDistribution { + nInt := math.NewInt(n) + amount0 := liquidityDistribution.TokenA.Amount.Quo(nInt) + amount1 := liquidityDistribution.TokenB.Amount.Quo(nInt) + + result := make([]LiquidityDistribution, n) + for i := range n { + + result[i] = LiquidityDistribution{ + TokenA: sdk.NewCoin(liquidityDistribution.TokenA.Denom, amount0), + TokenB: sdk.NewCoin(liquidityDistribution.TokenB.Denom, amount1), + } + + } + + return result + +} + +// State Parsers ////////////////////////////////////////////////////////////// + +func parseBool(b string) bool { + switch b { + case True: + return true + case False: + return false + default: + panic("invalid bool") + + } +} + +func parseLiquidityDistribution(liquidityDistribution string, pairID *dextypes.PairID) LiquidityDistribution { + tokenA := pairID.Token0 + tokenB := pairID.Token1 + switch liquidityDistribution { + case TokenA0TokenB0: + return LiquidityDistribution{TokenA: sdk.NewCoin(tokenA, math.ZeroInt()), TokenB: sdk.NewCoin(tokenB, math.ZeroInt())} + case TokenA0TokenB1: + return LiquidityDistribution{TokenA: sdk.NewCoin(tokenA, math.ZeroInt()), TokenB: sdk.NewCoin(tokenB, math.NewInt(1).Mul(BaseTokenAmountInt))} + case TokenA0TokenB2: + return LiquidityDistribution{TokenA: sdk.NewCoin(tokenA, math.ZeroInt()), TokenB: sdk.NewCoin(tokenB, math.NewInt(2).Mul(BaseTokenAmountInt))} + case TokenA1TokenB0: + return LiquidityDistribution{TokenA: sdk.NewCoin(tokenA, math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin(tokenB, math.ZeroInt())} + case TokenA1TokenB1: + return LiquidityDistribution{TokenA: sdk.NewCoin(tokenA, math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin(tokenB, math.NewInt(1).Mul(BaseTokenAmountInt))} + case TokenA1TokenB2: + return LiquidityDistribution{TokenA: sdk.NewCoin(tokenA, math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin(tokenB, math.NewInt(2).Mul(BaseTokenAmountInt))} + case TokenA2TokenB0: + return LiquidityDistribution{TokenA: sdk.NewCoin(tokenA, math.NewInt(2).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin(tokenB, math.ZeroInt())} + case TokenA2TokenB1: + return LiquidityDistribution{TokenA: sdk.NewCoin(tokenA, math.NewInt(2).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin(tokenB, math.NewInt(1).Mul(BaseTokenAmountInt))} + case TokenA2TokenB2: + return LiquidityDistribution{TokenA: sdk.NewCoin(tokenA, math.NewInt(2).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin(tokenB, math.NewInt(2).Mul(BaseTokenAmountInt))} + default: + panic("invalid liquidity distribution") + } +} + +// Misc. Helpers ////////////////////////////////////////////////////////////// + +func (s *DexStateTestSuite) intsEqual(field string, expected, actual math.Int) { + s.True(actual.Equal(expected), "For %v: Expected %v Got %v", field, expected, actual) +} + +func (s *DexStateTestSuite) makeDeposit(addr sdk.AccAddress, depositAmts LiquidityDistribution, disableAutoSwap bool) (*dextypes.MsgDepositResponse, error) { + coins := sdk.NewCoins(depositAmts.TokenA, depositAmts.TokenB) + s.FundAcc(addr, coins) + + return s.msgServer.Deposit(s.Ctx, &dextypes.MsgDeposit{ + + Creator: addr.String(), + Receiver: addr.String(), + TokenA: depositAmts.TokenA.Denom, + TokenB: depositAmts.TokenB.Denom, + AmountsA: []math.Int{depositAmts.TokenA.Amount}, + AmountsB: []math.Int{depositAmts.TokenB.Amount}, + TickIndexesAToB: []int64{DefaultTick}, + Fees: []uint64{DefaultFee}, + Options: []*dextypes.DepositOptions{{DisableAutoswap: disableAutoSwap}}, + }) +} + +func (s *DexStateTestSuite) makeDepositSuccess(addr sdk.AccAddress, depositAmts LiquidityDistribution, disableAutoSwap bool) *dextypes.MsgDepositResponse { + resp, err := s.makeDeposit(addr, depositAmts, disableAutoSwap) + s.NoError(err) + + return resp +} + +func calcDepositValueAsToken0(tick int64, amount0, amount1 math.Int) math_utils.PrecDec { + price1To0CenterTick := dextypes.MustCalcPrice(tick) + amount1ValueAsToken0 := price1To0CenterTick.MulInt(amount1) + depositValue := amount1ValueAsToken0.Add(math_utils.NewPrecDecFromInt(amount0)) + + return depositValue + +} + +func generatePairID(i int) *dextypes.PairID { + token0 := fmt.Sprintf("TokenA%d", i) + token1 := fmt.Sprintf("TokenB%d", i+1) + return dextypes.MustNewPairID(token0, token1) +} + +// Core Test Setup //////////////////////////////////////////////////////////// + +type DexStateTestSuite struct { + apptesting.KeeperTestHelper + msgServer dextypes.MsgServer + creator sdk.AccAddress + alice sdk.AccAddress +} + +func (s *DexStateTestSuite) SetupTest(t *testing.T) { + s.Setup() + s.creator = sdk.MustAccAddressFromBech32(sample.AccAddress()) + s.alice = sdk.MustAccAddressFromBech32(sample.AccAddress()) + + s.msgServer = dexkeeper.NewMsgServerImpl(s.App.DexKeeper) +} + +func cloneMap(original map[string]string) map[string]string { + // Create a new map + cloned := make(map[string]string) + // Copy each key-value pair from the original map to the new map + for key, value := range original { + cloned[key] = value + } + + return cloned +} + +type testParams struct { + field string + states []string +} + +func generatePermutations(testStates []testParams) []map[string]string { + result := make([]map[string]string, 0) + + var generate func(int, map[string]string) + generate = func(index int, current map[string]string) { + + // Base Case + if index == len(testStates) { + result = append(result, current) + return + } + + // Iterate through all possible values and create new states + for _, value := range testStates[index].states { + fieldName := testStates[index].field + temp := cloneMap(current) + temp[fieldName] = value + generate(index+1, temp) + } + + } + emptyMap := make(map[string]string) + generate(0, emptyMap) + + return result +} + +func removeDuplicateTests[T depositTestParams](testCases []T) []T { + result := make([]T, 0) + seenTCs := make(map[string]bool) + for _, tc := range testCases { + tcStr := fmt.Sprintf("%v", tc) + if _, ok := seenTCs[tcStr]; !ok { + result = append(result, tc) + } + seenTCs[tcStr] = true + } + return result +} From feed9f01ea08cff580e0db31b8c681ed44f866e2 Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Mon, 19 Aug 2024 14:26:12 -0400 Subject: [PATCH 07/21] test cleanup --- tests/dex/state_deposit_test.go | 61 +++++++++++++++++++++------------ tests/dex/state_setup_test.go | 7 ++-- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/tests/dex/state_deposit_test.go b/tests/dex/state_deposit_test.go index efa841b08..9e48978d5 100644 --- a/tests/dex/state_deposit_test.go +++ b/tests/dex/state_deposit_test.go @@ -1,7 +1,7 @@ package dex_state_test import ( - "fmt" + "strconv" "testing" "cosmossdk.io/math" @@ -13,17 +13,34 @@ import ( type depositTestParams struct { SharedParams // State Conditions - ExistingShareHolders string - LiquidityDistribution LiquidityDistribution - PoolValueIncrease LiquidityDistribution + ExistingShareHolders string + ExistingLiquidityDistribution LiquidityDistribution + PoolValueIncrease LiquidityDistribution // Message Variants DisableAutoswap bool FailTxOnBEL bool DepositAmounts LiquidityDistribution } +func (p depositTestParams) printTestInfo(t *testing.T) { + t.Logf(` + Existing Shareholders: %s + Existing Liquidity Distribution: %v + Pool Value Increase: %v + Disable Autoswap: %t + Fail Tx on BEL: %t + Deposit Amounts: %v`, + p.ExistingShareHolders, + p.ExistingLiquidityDistribution, + p.PoolValueIncrease, + p.DisableAutoswap, + p.FailTxOnBEL, + p.DepositAmounts, + ) +} + func (s *DexStateTestSuite) setupDepositState(params depositTestParams) { - liquidityDistr := params.LiquidityDistribution + liquidityDistr := params.ExistingLiquidityDistribution switch params.ExistingShareHolders { case None: @@ -53,8 +70,8 @@ func (s *DexStateTestSuite) setupDepositState(params depositTestParams) { func CalcTotalPreDepositLiquidity(params depositTestParams) LiquidityDistribution { return LiquidityDistribution{ - TokenA: params.LiquidityDistribution.TokenA.Add(params.PoolValueIncrease.TokenA), - TokenB: params.LiquidityDistribution.TokenB.Add(params.PoolValueIncrease.TokenB), + TokenA: params.ExistingLiquidityDistribution.TokenA.Add(params.PoolValueIncrease.TokenA), + TokenB: params.ExistingLiquidityDistribution.TokenB.Add(params.PoolValueIncrease.TokenB), } } @@ -78,11 +95,9 @@ func CalcDepositOutput(params depositTestParams) (resultAmountA, resultAmountB m return depositA, math.ZeroInt() // Pool has a ratio of A and B, deposit must match this ratio case existingA.IsPositive() && existingB.IsPositive(): - targetRatioA := math.LegacyNewDecFromInt(existingA).Quo(math.LegacyNewDecFromInt(existingB)) - maxAmountA := math.LegacyNewDecFromInt(depositB).Mul(targetRatioA).TruncateInt() + maxAmountA := math.LegacyNewDecFromInt(depositB).MulInt(existingA).QuoInt(existingB).TruncateInt() resultAmountA = math.MinInt(depositA, maxAmountA) - targetRatioB := math.LegacyOneDec().Quo(targetRatioA) - maxAmountB := math.LegacyNewDecFromInt(depositA).Mul(targetRatioB).TruncateInt() + maxAmountB := math.LegacyNewDecFromInt(depositA).MulInt(existingB).QuoInt(existingA).TruncateInt() resultAmountB = math.MinInt(depositB, maxAmountB) return resultAmountA, resultAmountB @@ -92,8 +107,8 @@ func CalcDepositOutput(params depositTestParams) (resultAmountA, resultAmountB m } func calcCurrentShareValue(params depositTestParams) math_utils.PrecDec { - initialValueA := params.LiquidityDistribution.TokenA.Amount - initialValueB := params.LiquidityDistribution.TokenB.Amount + initialValueA := params.ExistingLiquidityDistribution.TokenA.Amount + initialValueB := params.ExistingLiquidityDistribution.TokenB.Amount existingShares := calcDepositValueAsToken0(params.Tick, initialValueA, initialValueB).TruncateInt() if existingShares.IsZero() { @@ -104,6 +119,7 @@ func calcCurrentShareValue(params depositTestParams) math_utils.PrecDec { totalValueB := initialValueB.Add(params.PoolValueIncrease.TokenB.Amount) totalPreDepositValue := calcDepositValueAsToken0(params.Tick, totalValueA, totalValueB) + currentShareValue := math_utils.NewPrecDecFromInt(existingShares).Quo(totalPreDepositValue) return currentShareValue @@ -189,12 +205,12 @@ func HydrateDepositTestCase(params map[string]string, pairID *dextypes.PairID) d } return depositTestParams{ - ExistingShareHolders: existingShareHolders, - LiquidityDistribution: liquidityDistribution, - DisableAutoswap: parseBool(params["DisableAutoswap"]), - PoolValueIncrease: valueIncrease, - DepositAmounts: parseLiquidityDistribution(params["DepositAmounts"], pairID), - SharedParams: DefaultSharedParams, + ExistingShareHolders: existingShareHolders, + ExistingLiquidityDistribution: liquidityDistribution, + DisableAutoswap: parseBool(params["DisableAutoswap"]), + PoolValueIncrease: valueIncrease, + DepositAmounts: parseLiquidityDistribution(params["DepositAmounts"], pairID), + SharedParams: DefaultSharedParams, } } @@ -242,11 +258,12 @@ func TestDeposit(t *testing.T) { s.SetT(t) s.SetupTest(t) - for _, tc := range testCases { - testName := fmt.Sprintf("%v", tc) - t.Run(testName, func(t *testing.T) { + for i, tc := range testCases { + t.Run(strconv.Itoa(i), func(t *testing.T) { s.SetT(t) + tc.printTestInfo(t) + s.setupDepositState(tc) poolID, found := s.App.DexKeeper.GetPoolIDByParams(s.Ctx, tc.PairID, tc.Tick, tc.Fee) diff --git a/tests/dex/state_setup_test.go b/tests/dex/state_setup_test.go index 3a9f3aacf..fc4b5794b 100644 --- a/tests/dex/state_setup_test.go +++ b/tests/dex/state_setup_test.go @@ -59,9 +59,10 @@ const ( var BaseTokenAmountInt = math.NewInt(BaseTokenAmount) type SharedParams struct { - Tick int64 - Fee uint64 - PairID *dextypes.PairID + Tick int64 + Fee uint64 + PairID *dextypes.PairID + TestName string } var DefaultSharedParams SharedParams = SharedParams{ From 8b4e05a0cadb0e30d3c34f1d48eaf64742ef72b9 Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Mon, 19 Aug 2024 17:48:10 -0400 Subject: [PATCH 08/21] more test cleanup --- tests/dex/state_deposit_test.go | 53 +++++++++++++++++++++++------- tests/dex/state_setup_test.go | 58 ++++++++++++++++++++++++++------- 2 files changed, 87 insertions(+), 24 deletions(-) diff --git a/tests/dex/state_deposit_test.go b/tests/dex/state_deposit_test.go index 9e48978d5..c6dd77d5e 100644 --- a/tests/dex/state_deposit_test.go +++ b/tests/dex/state_deposit_test.go @@ -5,6 +5,7 @@ import ( "testing" "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" math_utils "github.com/neutron-org/neutron/v4/utils/math" dextypes "github.com/neutron-org/neutron/v4/x/dex/types" "github.com/stretchr/testify/require" @@ -40,19 +41,34 @@ func (p depositTestParams) printTestInfo(t *testing.T) { } func (s *DexStateTestSuite) setupDepositState(params depositTestParams) { + // NOTE: for setup we know the deposit will be completely used so we fund the accounts before the deposit + // so the expected account balance is unaffected. liquidityDistr := params.ExistingLiquidityDistribution switch params.ExistingShareHolders { case None: break case Creator: + coins := sdk.NewCoins(liquidityDistr.TokenA, liquidityDistr.TokenB) + s.FundAcc(s.creator, coins) + s.makeDepositSuccess(s.creator, liquidityDistr, false) case OneOther: + coins := sdk.NewCoins(liquidityDistr.TokenA, liquidityDistr.TokenB) + s.FundAcc(s.alice, coins) + s.makeDepositSuccess(s.alice, liquidityDistr, false) case OneOtherAndCreator: liqDistrArr := splitLiquidityDistribution(liquidityDistr, 2) - s.makeDepositSuccess(s.creator, liqDistrArr[1], false) - s.makeDepositSuccess(s.alice, liqDistrArr[0], false) + + coins := sdk.NewCoins(liqDistrArr[0].TokenA, liqDistrArr[0].TokenB) + s.FundAcc(s.creator, coins) + + coins = sdk.NewCoins(liqDistrArr[1].TokenA, liqDistrArr[1].TokenB) + s.FundAcc(s.alice, coins) + + s.makeDepositSuccess(s.creator, liqDistrArr[0], false) + s.makeDepositSuccess(s.alice, liqDistrArr[1], false) } // handle pool value increase @@ -65,6 +81,9 @@ func (s *DexStateTestSuite) setupDepositState(params depositTestParams) { pool.LowerTick0.ReservesMakerDenom = pool.LowerTick0.ReservesMakerDenom.Add(params.PoolValueIncrease.TokenA.Amount) pool.UpperTick1.ReservesMakerDenom = pool.UpperTick1.ReservesMakerDenom.Add(params.PoolValueIncrease.TokenB.Amount) s.App.DexKeeper.SetPool(s.Ctx, pool) + + // Add fund dex with the additional balance + s.App.BankKeeper.MintCoins(s.Ctx, dextypes.ModuleName, sdk.NewCoins(params.PoolValueIncrease.TokenA, params.PoolValueIncrease.TokenB)) } } @@ -185,6 +204,8 @@ func (s *DexStateTestSuite) handleBaseFailureCases(params depositTestParams, err s.T().Skip("Ending test due to expected error") } } + + s.NoError(err) } func HydrateDepositTestCase(params map[string]string, pairID *dextypes.PairID) depositTestParams { @@ -261,29 +282,26 @@ func TestDeposit(t *testing.T) { for i, tc := range testCases { t.Run(strconv.Itoa(i), func(t *testing.T) { s.SetT(t) - tc.printTestInfo(t) s.setupDepositState(tc) + s.fundCreatorBalanceDefault(tc.PairID) poolID, found := s.App.DexKeeper.GetPoolIDByParams(s.Ctx, tc.PairID, tc.Tick, tc.Fee) - if tc.ExistingShareHolders == None { // This is the ID that will be used when the pool is created poolID = s.App.DexKeeper.GetPoolCount(s.Ctx) } else { require.True(t, found, "Pool not found after deposit") } - poolDenom := dextypes.NewPoolDenom(poolID) - existingSharesOwned := s.App.BankKeeper.GetBalance(s.Ctx, s.creator, poolDenom) // Do the actual deposit resp, err := s.makeDeposit(s.creator, tc.DepositAmounts, tc.DisableAutoswap) + // Assert new state is correct s.handleBaseFailureCases(tc, err) - s.NoError(err) expectedDepositA, expectedDepositB, expectedShares := calcExpectedDepositAmounts(tc) @@ -291,12 +309,23 @@ func TestDeposit(t *testing.T) { s.intsEqual("Response Deposit0", expectedDepositA, resp.Reserve0Deposited[0]) s.intsEqual("Response Deposit1", expectedDepositB, resp.Reserve1Deposited[0]) - newSharesOwned := s.App.BankKeeper.GetBalance(s.Ctx, s.creator, poolDenom) - sharesIssued := newSharesOwned.Sub(existingSharesOwned) - s.intsEqual("Shares Issued", expectedShares, sharesIssued.Amount) + expectedTotalShares := existingSharesOwned.Amount.Add(expectedShares) + s.assertCreatorBalance(poolDenom, expectedTotalShares) + + // Assert Creator Balance is correct + expectedBalanceA := DefaultStartingBalanceInt.Sub(expectedDepositA) + expectedBalanceB := DefaultStartingBalanceInt.Sub(expectedDepositB) + s.assertCreatorBalance(tc.PairID.Token0, expectedBalanceA) + s.assertCreatorBalance(tc.PairID.Token1, expectedBalanceB) + + // Assert dex state is correct + dexBalanceBeforeDeposit := CalcTotalPreDepositLiquidity(tc) + expectedDexBalanceA := dexBalanceBeforeDeposit.TokenA.Amount.Add(expectedDepositA) + expectedDexBalanceB := dexBalanceBeforeDeposit.TokenB.Amount.Add(expectedDepositB) + s.assertPoolBalance(tc.PairID, tc.Tick, tc.Fee, expectedDexBalanceA, expectedDexBalanceB) + s.assertDexBalance(tc.PairID.Token0, expectedDexBalanceA) + s.assertDexBalance(tc.PairID.Token1, expectedDexBalanceB) - // TODO: balance checks for tokens - // TODO: maybe check actual dex state }) } diff --git a/tests/dex/state_setup_test.go b/tests/dex/state_setup_test.go index fc4b5794b..15f9ca514 100644 --- a/tests/dex/state_setup_test.go +++ b/tests/dex/state_setup_test.go @@ -51,12 +51,16 @@ const ( // Default Values const ( - BaseTokenAmount = 1_000_000 - DefaultTick = 0 - DefaultFee = 1 + BaseTokenAmount = 1_000_000 + DefaultTick = 0 + DefaultFee = 1 + DefaultStartingBalance = 10_000_000 ) -var BaseTokenAmountInt = math.NewInt(BaseTokenAmount) +var ( + BaseTokenAmountInt = math.NewInt(BaseTokenAmount) + DefaultStartingBalanceInt = math.NewInt(DefaultStartingBalance) +) type SharedParams struct { Tick int64 @@ -158,15 +162,7 @@ func parseLiquidityDistribution(liquidityDistribution string, pairID *dextypes.P } // Misc. Helpers ////////////////////////////////////////////////////////////// - -func (s *DexStateTestSuite) intsEqual(field string, expected, actual math.Int) { - s.True(actual.Equal(expected), "For %v: Expected %v Got %v", field, expected, actual) -} - func (s *DexStateTestSuite) makeDeposit(addr sdk.AccAddress, depositAmts LiquidityDistribution, disableAutoSwap bool) (*dextypes.MsgDepositResponse, error) { - coins := sdk.NewCoins(depositAmts.TokenA, depositAmts.TokenB) - s.FundAcc(addr, coins) - return s.msgServer.Deposit(s.Ctx, &dextypes.MsgDeposit{ Creator: addr.String(), @@ -203,6 +199,44 @@ func generatePairID(i int) *dextypes.PairID { return dextypes.MustNewPairID(token0, token1) } +func (s *DexStateTestSuite) fundCreatorBalanceDefault(pairID *dextypes.PairID) { + coins := sdk.NewCoins( + sdk.NewCoin(pairID.Token0, DefaultStartingBalanceInt), + sdk.NewCoin(pairID.Token1, DefaultStartingBalanceInt), + ) + s.FundAcc(s.creator, coins) +} + +// Assertions ///////////////////////////////////////////////////////////////// + +func (s *DexStateTestSuite) intsEqual(field string, expected, actual math.Int) { + s.True(actual.Equal(expected), "For %v: Expected %v Got %v", field, expected, actual) +} + +func (s *DexStateTestSuite) assertBalance(addr sdk.AccAddress, denom string, expected math.Int) { + trueBalance := s.App.BankKeeper.GetBalance(s.Ctx, addr, denom) + s.intsEqual(fmt.Sprintf("Balance %s", denom), expected, trueBalance.Amount) +} + +func (s *DexStateTestSuite) assertCreatorBalance(denom string, expected math.Int) { + s.assertBalance(s.creator, denom, expected) +} + +func (s *DexStateTestSuite) assertDexBalance(denom string, expected math.Int) { + s.assertBalance(s.App.AccountKeeper.GetModuleAddress("dex"), denom, expected) +} + +func (s *DexStateTestSuite) assertPoolBalance(pairID *dextypes.PairID, tick int64, fee uint64, expectedA, expectedB math.Int) { + pool, found := s.App.DexKeeper.GetPool(s.Ctx, pairID, tick, fee) + s.True(found, "Pool not found") + + reservesA := pool.LowerTick0.ReservesMakerDenom + reservesB := pool.UpperTick1.ReservesMakerDenom + + s.intsEqual("Pool ReservesA", expectedA, reservesA) + s.intsEqual("Pool ReservesB", expectedB, reservesB) +} + // Core Test Setup //////////////////////////////////////////////////////////// type DexStateTestSuite struct { From 8dd318f737c626baf2202813c860370cc866a990 Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Mon, 19 Aug 2024 18:20:08 -0400 Subject: [PATCH 09/21] fmt --- tests/dex/state_deposit_test.go | 24 +++++++--------- tests/dex/state_setup_test.go | 51 +++++++++++++++------------------ 2 files changed, 34 insertions(+), 41 deletions(-) diff --git a/tests/dex/state_deposit_test.go b/tests/dex/state_deposit_test.go index c6dd77d5e..01908581e 100644 --- a/tests/dex/state_deposit_test.go +++ b/tests/dex/state_deposit_test.go @@ -6,9 +6,10 @@ import ( "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + math_utils "github.com/neutron-org/neutron/v4/utils/math" dextypes "github.com/neutron-org/neutron/v4/x/dex/types" - "github.com/stretchr/testify/require" ) type depositTestParams struct { @@ -83,7 +84,8 @@ func (s *DexStateTestSuite) setupDepositState(params depositTestParams) { s.App.DexKeeper.SetPool(s.Ctx, pool) // Add fund dex with the additional balance - s.App.BankKeeper.MintCoins(s.Ctx, dextypes.ModuleName, sdk.NewCoins(params.PoolValueIncrease.TokenA, params.PoolValueIncrease.TokenB)) + err := s.App.BankKeeper.MintCoins(s.Ctx, dextypes.ModuleName, sdk.NewCoins(params.PoolValueIncrease.TokenA, params.PoolValueIncrease.TokenB)) + s.NoError(err) } } @@ -103,7 +105,7 @@ func CalcDepositOutput(params depositTestParams) (resultAmountA, resultAmountB m existingB := existingLiquidity.TokenB.Amount switch { - //Pool is empty can deposit full amounts + // Pool is empty can deposit full amounts case existingA.IsZero() && existingB.IsZero(): return depositA, depositB // Pool only has TokenB, can deposit all of depositB @@ -170,7 +172,6 @@ func calcAutoSwapResidualValue(params depositTestParams, residual0, residual1 ma } func calcExpectedDepositAmounts(params depositTestParams) (tokenAAmount, tokenBAmount, sharesIssued math.Int) { - amountAWithoutAutoswap, amountBWithoutAutoswap := CalcDepositOutput(params) sharesIssuedWithoutAutoswap := calcDepositValue(params, amountAWithoutAutoswap, amountBWithoutAutoswap) @@ -208,7 +209,7 @@ func (s *DexStateTestSuite) handleBaseFailureCases(params depositTestParams, err s.NoError(err) } -func HydrateDepositTestCase(params map[string]string, pairID *dextypes.PairID) depositTestParams { +func hydrateDepositTestCase(params map[string]string, pairID *dextypes.PairID) depositTestParams { existingShareHolders := params["ExistingShareHolders"] var liquidityDistribution LiquidityDistribution @@ -235,11 +236,11 @@ func HydrateDepositTestCase(params map[string]string, pairID *dextypes.PairID) d } } -func HydrateAllDepositTestCases(paramsList []map[string]string) []depositTestParams { +func hydrateAllDepositTestCases(paramsList []map[string]string) []depositTestParams { allTCs := make([]depositTestParams, 0) for i, paramsRaw := range paramsList { pairID := generatePairID(i) - tc := HydrateDepositTestCase(paramsRaw, pairID) + tc := hydrateDepositTestCase(paramsRaw, pairID) tc.PairID = pairID allTCs = append(allTCs, tc) } @@ -273,11 +274,11 @@ func TestDeposit(t *testing.T) { }}, } testCasesRaw := generatePermutations(testParams) - testCases := HydrateAllDepositTestCases(testCasesRaw) + testCases := hydrateAllDepositTestCases(testCasesRaw) s := new(DexStateTestSuite) s.SetT(t) - s.SetupTest(t) + s.SetupTest() for i, tc := range testCases { t.Run(strconv.Itoa(i), func(t *testing.T) { @@ -305,7 +306,7 @@ func TestDeposit(t *testing.T) { expectedDepositA, expectedDepositB, expectedShares := calcExpectedDepositAmounts(tc) - //Check that response is correct + // Check that response is correct s.intsEqual("Response Deposit0", expectedDepositA, resp.Reserve0Deposited[0]) s.intsEqual("Response Deposit1", expectedDepositB, resp.Reserve1Deposited[0]) @@ -325,9 +326,6 @@ func TestDeposit(t *testing.T) { s.assertPoolBalance(tc.PairID, tc.Tick, tc.Fee, expectedDexBalanceA, expectedDexBalanceB) s.assertDexBalance(tc.PairID.Token0, expectedDexBalanceA) s.assertDexBalance(tc.PairID.Token1, expectedDexBalanceB) - }) - } - } diff --git a/tests/dex/state_setup_test.go b/tests/dex/state_setup_test.go index 15f9ca514..a4588fc50 100644 --- a/tests/dex/state_setup_test.go +++ b/tests/dex/state_setup_test.go @@ -2,10 +2,10 @@ package dex_state_test import ( "fmt" - "testing" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/neutron-org/neutron/v4/testutil/apptesting" "github.com/neutron-org/neutron/v4/testutil/common/sample" math_utils "github.com/neutron-org/neutron/v4/utils/math" @@ -17,36 +17,38 @@ import ( // Bools const ( - True string = "True" - False = "False" + True = "True" + False = "False" ) // Percents const ( - ZeroPCT string = "0" - FiftyPCT = "50" - HundredPct = "100" + ZeroPCT = "0" + FiftyPCT = "50" + HundredPct = "100" ) // ExistingShareHolders const ( - None string = "None" - Creator = "Creator" - OneOther = "OneOther" - OneOtherAndCreator = "OneOtherAndCreator" + None = "None" + Creator = "Creator" + OneOther = "OneOther" + OneOtherAndCreator = "OneOtherAndCreator" ) // LiquidityDistribution +// +//nolint:gosec const ( - TokenA0TokenB0 string = "TokenA0TokenB0" - TokenA0TokenB1 = "TokenA0TokenB1" - TokenA0TokenB2 = "TokenA0TokenB2" - TokenA1TokenB0 = "TokenA1TokenB0" - TokenA1TokenB1 = "TokenA1TokenB1" - TokenA1TokenB2 = "TokenA1TokenB2" - TokenA2TokenB0 = "TokenA2TokenB0" - TokenA2TokenB1 = "TokenA2TokenB1" - TokenA2TokenB2 = "TokenA2TokenB2" + TokenA0TokenB0 = "TokenA0TokenB0" + TokenA0TokenB1 = "TokenA0TokenB1" + TokenA0TokenB2 = "TokenA0TokenB2" + TokenA1TokenB0 = "TokenA1TokenB0" + TokenA1TokenB1 = "TokenA1TokenB1" + TokenA1TokenB2 = "TokenA1TokenB2" + TokenA2TokenB0 = "TokenA2TokenB0" + TokenA2TokenB1 = "TokenA2TokenB1" + TokenA2TokenB2 = "TokenA2TokenB2" ) // Default Values @@ -69,7 +71,7 @@ type SharedParams struct { TestName string } -var DefaultSharedParams SharedParams = SharedParams{ +var DefaultSharedParams = SharedParams{ Tick: DefaultTick, Fee: DefaultFee, } @@ -108,16 +110,13 @@ func splitLiquidityDistribution(liquidityDistribution LiquidityDistribution, n i result := make([]LiquidityDistribution, n) for i := range n { - result[i] = LiquidityDistribution{ TokenA: sdk.NewCoin(liquidityDistribution.TokenA.Denom, amount0), TokenB: sdk.NewCoin(liquidityDistribution.TokenB.Denom, amount1), } - } return result - } // State Parsers ////////////////////////////////////////////////////////////// @@ -164,7 +163,6 @@ func parseLiquidityDistribution(liquidityDistribution string, pairID *dextypes.P // Misc. Helpers ////////////////////////////////////////////////////////////// func (s *DexStateTestSuite) makeDeposit(addr sdk.AccAddress, depositAmts LiquidityDistribution, disableAutoSwap bool) (*dextypes.MsgDepositResponse, error) { return s.msgServer.Deposit(s.Ctx, &dextypes.MsgDeposit{ - Creator: addr.String(), Receiver: addr.String(), TokenA: depositAmts.TokenA.Denom, @@ -190,7 +188,6 @@ func calcDepositValueAsToken0(tick int64, amount0, amount1 math.Int) math_utils. depositValue := amount1ValueAsToken0.Add(math_utils.NewPrecDecFromInt(amount0)) return depositValue - } func generatePairID(i int) *dextypes.PairID { @@ -246,7 +243,7 @@ type DexStateTestSuite struct { alice sdk.AccAddress } -func (s *DexStateTestSuite) SetupTest(t *testing.T) { +func (s *DexStateTestSuite) SetupTest() { s.Setup() s.creator = sdk.MustAccAddressFromBech32(sample.AccAddress()) s.alice = sdk.MustAccAddressFromBech32(sample.AccAddress()) @@ -275,7 +272,6 @@ func generatePermutations(testStates []testParams) []map[string]string { var generate func(int, map[string]string) generate = func(index int, current map[string]string) { - // Base Case if index == len(testStates) { result = append(result, current) @@ -289,7 +285,6 @@ func generatePermutations(testStates []testParams) []map[string]string { temp[fieldName] = value generate(index+1, temp) } - } emptyMap := make(map[string]string) generate(0, emptyMap) From 314504486fcc4b97f4e8cdc0c1746b937a84cc2b Mon Sep 17 00:00:00 2001 From: swelf Date: Mon, 9 Sep 2024 17:44:35 +0300 Subject: [PATCH 10/21] all tests finished --- tests/dex/state_cancel_test.go | 190 +++++++++++ tests/dex/state_deposit_test.go | 33 +- .../dex/state_place_limit_order_maker_test.go | 308 ++++++++++++++++++ .../dex/state_place_limit_order_taker_test.go | 289 ++++++++++++++++ tests/dex/state_setup_test.go | 200 +++++++++++- tests/dex/state_withdraw_test.go | 173 ++++++++++ tests/dex/state_withdrawlimitorder_test.go | 249 ++++++++++++++ 7 files changed, 1412 insertions(+), 30 deletions(-) create mode 100644 tests/dex/state_cancel_test.go create mode 100644 tests/dex/state_place_limit_order_maker_test.go create mode 100644 tests/dex/state_place_limit_order_taker_test.go create mode 100644 tests/dex/state_withdraw_test.go create mode 100644 tests/dex/state_withdrawlimitorder_test.go diff --git a/tests/dex/state_cancel_test.go b/tests/dex/state_cancel_test.go new file mode 100644 index 000000000..4609c518b --- /dev/null +++ b/tests/dex/state_cancel_test.go @@ -0,0 +1,190 @@ +package dex_state_test + +import ( + "cosmossdk.io/math" + "errors" + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + dextypes "github.com/neutron-org/neutron/v4/x/dex/types" + "strconv" + "testing" + "time" +) + +type cancelLimitOrderTestParams struct { + // State Conditions + SharedParams + ExistingTokenAHolders string + Filled int + WithdrawnCreator bool + WithdrawnOneOther bool + Expired bool + OrderType int32 // JIT, GTT, GTC +} + +func (p cancelLimitOrderTestParams) printTestInfo(t *testing.T) { + t.Logf(` + Existing Shareholders: %s + Filled: %v + WithdrawnCreator: %v + WithdrawnOneOther: %t + Expired: %t + OrderType: %v`, + p.ExistingTokenAHolders, + p.Filled, + p.WithdrawnCreator, + p.WithdrawnOneOther, + p.Expired, + p.OrderType, + ) +} + +func hydrateCancelLoTestCase(params map[string]string) cancelLimitOrderTestParams { + return cancelLimitOrderTestParams{ + ExistingTokenAHolders: params["ExistingTokenAHolders"], + Filled: parseInt(params["Filled"]), + WithdrawnCreator: parseBool(params["WithdrawnCreator"]), + WithdrawnOneOther: parseBool(params["WithdrawnOneOther"]), + Expired: parseBool(params["Expired"]), + OrderType: dextypes.LimitOrderType_value[params["OrderType"]], + } +} + +func (s *DexStateTestSuite) setupCancelTest(params cancelLimitOrderTestParams) *dextypes.LimitOrderTranche { + coinA := sdk.NewCoin(params.PairID.Token0, BaseTokenAmountInt) + coinB := sdk.NewCoin(params.PairID.Token1, BaseTokenAmountInt) + s.FundAcc(s.creator, sdk.NewCoins(coinA)) + var expTime *time.Time + if params.OrderType == int32(dextypes.LimitOrderType_GOOD_TIL_TIME) { + t := time.Now() + expTime = &t + } + res := s.makePlaceLOSuccess(s.creator, coinA, coinB.Denom, DefaultSellPrice, dextypes.LimitOrderType(params.OrderType), expTime) + + totalDeposited := BaseTokenAmountInt + if params.ExistingTokenAHolders == OneOtherAndCreatorLO { + totalDeposited = totalDeposited.MulRaw(2) + s.FundAcc(s.alice, sdk.NewCoins(coinA)) + s.makePlaceLOSuccess(s.alice, coinA, coinB.Denom, DefaultSellPrice, dextypes.LimitOrderType(params.OrderType), expTime) + } + + if params.Filled > 0 { + s.FundAcc(s.bob, sdk.NewCoins(coinB).MulInt(math.NewInt(10))) + fillAmount := totalDeposited.MulRaw(int64(params.Filled)).QuoRaw(100) + _, err := s.makePlaceTakerLO(s.bob, coinB, coinA.Denom, DefaultBuyPriceTaker, dextypes.LimitOrderType_IMMEDIATE_OR_CANCEL, &fillAmount) + s.NoError(err) + } + + if params.WithdrawnCreator { + s.makeWithdrawFilledSuccess(s.creator, res.TrancheKey) + } + + if params.WithdrawnOneOther { + s.makeWithdrawFilledSuccess(s.alice, res.TrancheKey) + } + + if params.Expired { + s.App.DexKeeper.PurgeExpiredLimitOrders(s.Ctx, time.Now()) + } + tick, err := dextypes.CalcTickIndexFromPrice(DefaultStartPrice) + s.NoError(err) + tranches, _ := s.App.DexKeeper.LimitOrderTrancheAll(s.Ctx, &dextypes.QueryAllLimitOrderTrancheRequest{ + PairId: params.PairID.CanonicalString(), + TokenIn: params.PairID.Token0, + Pagination: nil, + }) + fmt.Println(tranches) + req := dextypes.QueryGetLimitOrderTrancheRequest{ + PairId: params.PairID.CanonicalString(), + TickIndex: -1 * tick, + TokenIn: params.PairID.Token0, + TrancheKey: res.TrancheKey, + } + fmt.Println(req) + tranchResp, err := s.App.DexKeeper.LimitOrderTranche(s.Ctx, &req) + s.NoError(err) + + return tranchResp.LimitOrderTranche +} + +func hydrateAllCancelLoTestCases(paramsList []map[string]string) []cancelLimitOrderTestParams { + allTCs := make([]cancelLimitOrderTestParams, 0) + for i, paramsRaw := range paramsList { + pairID := generatePairID(i) + tc := hydrateCancelLoTestCase(paramsRaw) + tc.PairID = pairID + allTCs = append(allTCs, tc) + } + + return removeRedundantCancelLOTests(allTCs) +} + +func removeRedundantCancelLOTests(params []cancelLimitOrderTestParams) []cancelLimitOrderTestParams { + newParams := make([]cancelLimitOrderTestParams, 0) + for _, p := range params { + // it's impossible to withdraw 0 filled + // error checks is not in a scope of the testcase (see withdraw filled test) + if p.Filled == 0 && (p.WithdrawnOneOther || p.WithdrawnCreator) { + continue + } + if p.Expired && p.OrderType == int32(dextypes.LimitOrderType_GOOD_TIL_CANCELLED) { + continue + } + if p.WithdrawnOneOther && p.ExistingTokenAHolders == CreatorLO { + continue + } + if p.ExistingTokenAHolders == OneOtherAndCreatorLO && p.OrderType != int32(dextypes.LimitOrderType_GOOD_TIL_CANCELLED) { + // user tranches combined into tranches only for LimitOrderType_GOOD_TIL_CANCELLED + // it does not make any sense to create two tranches + continue + } + newParams = append(newParams, p) + } + return newParams +} + +func (s *DexStateTestSuite) handleCancelErrors(params cancelLimitOrderTestParams, err error) { + if params.Expired { + if errors.Is(dextypes.ErrActiveLimitOrderNotFound, err) { + s.T().Skip() + } + } + s.NoError(err) +} + +func TestCancel(t *testing.T) { + testParams := []testParams{ + {field: "ExistingTokenAHolders", states: []string{CreatorLO, OneOtherAndCreatorLO}}, + {field: "Filled", states: []string{ZeroPCT, FiftyPCT, HundredPct}}, + {field: "WithdrawnCreator", states: []string{True, False}}, + {field: "WithdrawnOneOther", states: []string{True, False}}, + {field: "OrderType", states: []string{ + dextypes.LimitOrderType_name[int32(dextypes.LimitOrderType_GOOD_TIL_CANCELLED)], + dextypes.LimitOrderType_name[int32(dextypes.LimitOrderType_GOOD_TIL_TIME)], + dextypes.LimitOrderType_name[int32(dextypes.LimitOrderType_JUST_IN_TIME)], + }}, + {field: "Expired", states: []string{True, False}}, + } + testCasesRaw := generatePermutations(testParams) + testCases := hydrateAllCancelLoTestCases(testCasesRaw) + + s := new(DexStateTestSuite) + s.SetT(t) + s.SetupTest() + + for i, tc := range testCases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + s.SetT(t) + tc.printTestInfo(t) + + initialTrancheKey := s.setupCancelTest(tc) + fmt.Println(initialTrancheKey) + + resp, err := s.makeCancel(s.creator, initialTrancheKey.Key.TrancheKey) + s.handleCancelErrors(tc, err) + fmt.Println("resp", resp) + fmt.Println("err", err) + + }) + } +} diff --git a/tests/dex/state_deposit_test.go b/tests/dex/state_deposit_test.go index 01908581e..a7670d621 100644 --- a/tests/dex/state_deposit_test.go +++ b/tests/dex/state_deposit_test.go @@ -12,12 +12,16 @@ import ( dextypes "github.com/neutron-org/neutron/v4/x/dex/types" ) -type depositTestParams struct { +type DepositState struct { SharedParams // State Conditions ExistingShareHolders string ExistingLiquidityDistribution LiquidityDistribution PoolValueIncrease LiquidityDistribution +} + +type depositTestParams struct { + DepositState // Message Variants DisableAutoswap bool FailTxOnBEL bool @@ -41,7 +45,7 @@ func (p depositTestParams) printTestInfo(t *testing.T) { ) } -func (s *DexStateTestSuite) setupDepositState(params depositTestParams) { +func (s *DexStateTestSuite) setupDepositState(params DepositState) { // NOTE: for setup we know the deposit will be completely used so we fund the accounts before the deposit // so the expected account balance is unaffected. liquidityDistr := params.ExistingLiquidityDistribution @@ -162,7 +166,7 @@ func calcAutoSwapResidualValue(params depositTestParams, residual0, residual1 ma case residual0.IsPositive(): return swapFeeDeduction.MulInt(residual0) case residual1.IsPositive(): - price1To0CenterTick := dextypes.MustCalcPrice(params.Tick) + price1To0CenterTick := dextypes.MustCalcPrice(-1 * params.Tick) token1AsToken0 := price1To0CenterTick.MulInt(residual1) return swapFeeDeduction.Mul(token1AsToken0) default: @@ -227,12 +231,14 @@ func hydrateDepositTestCase(params map[string]string, pairID *dextypes.PairID) d } return depositTestParams{ - ExistingShareHolders: existingShareHolders, - ExistingLiquidityDistribution: liquidityDistribution, - DisableAutoswap: parseBool(params["DisableAutoswap"]), - PoolValueIncrease: valueIncrease, - DepositAmounts: parseLiquidityDistribution(params["DepositAmounts"], pairID), - SharedParams: DefaultSharedParams, + DepositState: DepositState{ + ExistingShareHolders: existingShareHolders, + ExistingLiquidityDistribution: liquidityDistribution, + PoolValueIncrease: valueIncrease, + SharedParams: DefaultSharedParams, + }, + DepositAmounts: parseLiquidityDistribution(params["DepositAmounts"], pairID), + DisableAutoswap: parseBool(params["DisableAutoswap"]), } } @@ -272,6 +278,7 @@ func TestDeposit(t *testing.T) { TokenA1TokenB2, TokenA2TokenB2, }}, + // TODO: test over a list of Fees/Ticks } testCasesRaw := generatePermutations(testParams) testCases := hydrateAllDepositTestCases(testCasesRaw) @@ -285,7 +292,7 @@ func TestDeposit(t *testing.T) { s.SetT(t) tc.printTestInfo(t) - s.setupDepositState(tc) + s.setupDepositState(tc.DepositState) s.fundCreatorBalanceDefault(tc.PairID) poolID, found := s.App.DexKeeper.GetPoolIDByParams(s.Ctx, tc.PairID, tc.Tick, tc.Fee) @@ -299,7 +306,7 @@ func TestDeposit(t *testing.T) { existingSharesOwned := s.App.BankKeeper.GetBalance(s.Ctx, s.creator, poolDenom) // Do the actual deposit - resp, err := s.makeDeposit(s.creator, tc.DepositAmounts, tc.DisableAutoswap) + resp, err := s.makeDepositDefault(s.creator, tc.DepositAmounts, tc.DisableAutoswap) // Assert new state is correct s.handleBaseFailureCases(tc, err) @@ -307,8 +314,8 @@ func TestDeposit(t *testing.T) { expectedDepositA, expectedDepositB, expectedShares := calcExpectedDepositAmounts(tc) // Check that response is correct - s.intsEqual("Response Deposit0", expectedDepositA, resp.Reserve0Deposited[0]) - s.intsEqual("Response Deposit1", expectedDepositB, resp.Reserve1Deposited[0]) + s.intsApproxEqual("Response Deposit0", expectedDepositA, resp.Reserve0Deposited[0], 1) + s.intsApproxEqual("Response Deposit1", expectedDepositB, resp.Reserve1Deposited[0], 1) expectedTotalShares := existingSharesOwned.Amount.Add(expectedShares) s.assertCreatorBalance(poolDenom, expectedTotalShares) diff --git a/tests/dex/state_place_limit_order_maker_test.go b/tests/dex/state_place_limit_order_maker_test.go new file mode 100644 index 000000000..6a7fb8d5d --- /dev/null +++ b/tests/dex/state_place_limit_order_maker_test.go @@ -0,0 +1,308 @@ +package dex_state_test + +import ( + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + math_utils "github.com/neutron-org/neutron/v4/utils/math" + dextypes "github.com/neutron-org/neutron/v4/x/dex/types" + "strconv" + "testing" + "time" +) + +// ExistingTokenAHolders +const ( + NoneLO = "NoneLO" + CreatorLO = "CreatorLO" + OneOtherLO = "OneOtherLO" + OneOtherAndCreatorLO = "OneOtherAndCreatorLO" +) + +// tests autoswap, BehindEnemyLineLessLimit and BehindEnemyLineGreaterLimit only makes sense when ExistingTokenAHolders==NoneLO +// BehindEnemyLine +const ( + BehindEnemyLineNo = "BehindEnemyLinesNo" // no opposite liquidity to trigger swap + BehindEnemyLineLessLimit = "BehindEnemyLinesLessLimit" // not enough opposite liquidity to swap maker lo fully + BehindEnemyLineGreaterLimit = "BehindEnemyLinesGreaterLimit" // enough liquidity to swap make fully +) + +const ( + DefaultSellPrice = "2" + DefaultBuyPriceTaker = "0.4" // 1/(DefaultSellPrice+0.5) immediately trade over DefaultSellPrice maker order +) + +const MakerAmountIn = 1_000_000 + +type placeLimitOrderMakerTestParams struct { + // State Conditions + SharedParams + ExistingLOLiquidityDistribution LiquidityDistribution + ExistingTokenAHolders string + BehindEnemyLine string + PreexistingTraded bool + // Message Variants + OrderType int32 // JIT, GTT, GTC +} + +func (p placeLimitOrderMakerTestParams) printTestInfo(t *testing.T) { + t.Logf(` + Existing ExistingTokenAHolders: %s + BehindEnemyLine: %v + Pre-existing Traded: %v + Order Type: %v`, + p.ExistingTokenAHolders, + p.BehindEnemyLine, + p.PreexistingTraded, + dextypes.LimitOrderType_name[p.OrderType], + ) +} + +func parseLOLiquidityDistribution(existingShareHolders, behindEnemyLine string, pairID *dextypes.PairID) LiquidityDistribution { + tokenA := pairID.Token0 + tokenB := pairID.Token1 + switch { + case existingShareHolders == NoneLO && behindEnemyLine == BehindEnemyLineLessLimit: + // half "taker" deposit. We buy all of it by placing opposite maker order + return LiquidityDistribution{ + TokenA: sdk.NewCoin(tokenA, math.ZeroInt()), + TokenB: sdk.NewCoin(tokenB, BaseTokenAmountInt.QuoRaw(2)), + } + case existingShareHolders == NoneLO && behindEnemyLine == BehindEnemyLineGreaterLimit: + // double "taker" deposit. We spend whole limit to partially consume LO. + return LiquidityDistribution{TokenA: sdk.NewCoin(tokenA, math.ZeroInt()), TokenB: sdk.NewCoin(tokenB, math.NewInt(4).Mul(BaseTokenAmountInt))} + default: + return LiquidityDistribution{TokenA: sdk.NewCoin(tokenA, math.NewInt(1).Mul(BaseTokenAmountInt)), TokenB: sdk.NewCoin(tokenB, math.NewInt(1).Mul(BaseTokenAmountInt))} + } +} + +func removeRedundantPlaceLOMakerTests(tcs []placeLimitOrderMakerTestParams) []placeLimitOrderMakerTestParams { + // here we remove impossible cases such two side LO at the "same" (1/-1, 2/-2) ticks + result := make([]placeLimitOrderMakerTestParams, 0) + for _, tc := range tcs { + if tc.ExistingTokenAHolders != NoneLO && tc.BehindEnemyLine != BehindEnemyLineNo { + continue + } + if tc.PreexistingTraded && tc.ExistingTokenAHolders != OneOtherLO { + // PreexistingTraded only make sense in case `tc.ExistingTokenAHolders == OneOtherLO` + continue + } + result = append(result, tc) + } + return result +} + +func hydratePlaceLOMakerTestCase(params map[string]string, pairID *dextypes.PairID) placeLimitOrderMakerTestParams { + liquidityDistribution := parseLOLiquidityDistribution(params["ExistingTokenAHolders"], params["BehindEnemyLines"], pairID) + return placeLimitOrderMakerTestParams{ + ExistingLOLiquidityDistribution: liquidityDistribution, + ExistingTokenAHolders: params["ExistingTokenAHolders"], + BehindEnemyLine: params["BehindEnemyLines"], + PreexistingTraded: parseBool(params["PreexistingTraded"]), + OrderType: dextypes.LimitOrderType_value[params["OrderType"]], + } +} + +func hydrateAllPlaceLOMakerTestCases(paramsList []map[string]string) []placeLimitOrderMakerTestParams { + allTCs := make([]placeLimitOrderMakerTestParams, 0) + for i, paramsRaw := range paramsList { + pairID := generatePairID(i) + tc := hydratePlaceLOMakerTestCase(paramsRaw, pairID) + tc.PairID = pairID + allTCs = append(allTCs, tc) + } + + return removeRedundantPlaceLOMakerTests(allTCs) +} + +func (s *DexStateTestSuite) setupLoState(params placeLimitOrderMakerTestParams) (trancheKey string) { + liquidityDistr := params.ExistingLOLiquidityDistribution + coins := sdk.NewCoins(liquidityDistr.TokenA, liquidityDistr.TokenB) + switch params.BehindEnemyLine { + case BehindEnemyLineNo: + switch params.ExistingTokenAHolders { + case OneOtherLO: + s.FundAcc(s.alice, coins) + res := s.makePlaceLOSuccess(s.alice, params.ExistingLOLiquidityDistribution.TokenA, params.ExistingLOLiquidityDistribution.TokenB.Denom, DefaultSellPrice, dextypes.LimitOrderType_GOOD_TIL_CANCELLED, nil) + trancheKey = res.TrancheKey + + case OneOtherAndCreatorLO: + s.FundAcc(s.alice, coins) + res := s.makePlaceLOSuccess(s.alice, params.ExistingLOLiquidityDistribution.TokenA, params.ExistingLOLiquidityDistribution.TokenB.Denom, DefaultSellPrice, dextypes.LimitOrderType_GOOD_TIL_CANCELLED, nil) + trancheKey = res.TrancheKey + + s.FundAcc(s.creator, coins) + s.makePlaceLOSuccess(s.creator, params.ExistingLOLiquidityDistribution.TokenA, params.ExistingLOLiquidityDistribution.TokenB.Denom, DefaultSellPrice, dextypes.LimitOrderType_GOOD_TIL_CANCELLED, nil) + + case CreatorLO: + s.FundAcc(s.creator, coins) + res := s.makePlaceLOSuccess(s.creator, params.ExistingLOLiquidityDistribution.TokenA, params.ExistingLOLiquidityDistribution.TokenB.Denom, DefaultSellPrice, dextypes.LimitOrderType_GOOD_TIL_CANCELLED, nil) + trancheKey = res.TrancheKey + } + + if params.PreexistingTraded { + s.FundAcc(s.bob, coins) + // bob trades over the tranche + InTokenBAmount := sdk.NewCoin(params.ExistingLOLiquidityDistribution.TokenB.Denom, BaseTokenAmountInt.QuoRaw(2)) + s.makePlaceLOSuccess(s.alice, InTokenBAmount, params.ExistingLOLiquidityDistribution.TokenA.Denom, DefaultBuyPriceTaker, dextypes.LimitOrderType_GOOD_TIL_CANCELLED, nil) + } + case BehindEnemyLineLessLimit: + s.FundAcc(s.alice, coins) + s.makePlaceLOSuccess(s.alice, params.ExistingLOLiquidityDistribution.TokenB, params.ExistingLOLiquidityDistribution.TokenA.Denom, DefaultBuyPriceTaker, dextypes.LimitOrderType_GOOD_TIL_CANCELLED, nil) + case BehindEnemyLineGreaterLimit: + s.FundAcc(s.alice, coins) + s.makePlaceLOSuccess(s.alice, params.ExistingLOLiquidityDistribution.TokenB, params.ExistingLOLiquidityDistribution.TokenA.Denom, DefaultBuyPriceTaker, dextypes.LimitOrderType_GOOD_TIL_CANCELLED, nil) + } + return trancheKey +} + +// assertLiquidity checks the amount of tokens at dex balance exactly equals the amount in all tranches (active + inactive) +// TODO: add AMM pools to check +func (s *DexStateTestSuite) assertLiquidity(id dextypes.PairID) { + TokenAInReserves := math.ZeroInt() + TokenBInReserves := math.ZeroInt() + + // Active tranches A -> B + tranches, err := s.App.DexKeeper.LimitOrderTrancheAll(s.Ctx, &dextypes.QueryAllLimitOrderTrancheRequest{ + PairId: id.CanonicalString(), + TokenIn: id.Token0, + Pagination: nil, + }) + s.Require().NoError(err) + for _, t := range tranches.LimitOrderTranche { + TokenAInReserves = TokenAInReserves.Add(t.ReservesMakerDenom) + TokenBInReserves = TokenBInReserves.Add(t.ReservesTakerDenom) + } + + // Active tranches B -> A + tranches, err = s.App.DexKeeper.LimitOrderTrancheAll(s.Ctx, &dextypes.QueryAllLimitOrderTrancheRequest{ + PairId: id.CanonicalString(), + TokenIn: id.Token1, + Pagination: nil, + }) + s.Require().NoError(err) + for _, t := range tranches.LimitOrderTranche { + TokenAInReserves = TokenAInReserves.Add(t.ReservesTakerDenom) + TokenBInReserves = TokenBInReserves.Add(t.ReservesMakerDenom) + } + + // Inactive tranches (expired or filled) + // TODO: since it's impossible to filter tranches against a specific pair in a request, pagination request may be needed in some cases. Add pagination + inactiveTranches, err := s.App.DexKeeper.InactiveLimitOrderTrancheAll(s.Ctx, &dextypes.QueryAllInactiveLimitOrderTrancheRequest{ + Pagination: nil, + }) + s.Require().NoError(err) + for _, t := range inactiveTranches.InactiveLimitOrderTranche { + // A -> B + if t.Key.TradePairId.MakerDenom == id.Token0 || t.Key.TradePairId.TakerDenom == id.Token1 { + TokenAInReserves = TokenAInReserves.Add(t.ReservesMakerDenom) + TokenBInReserves = TokenBInReserves.Add(t.ReservesTakerDenom) + } + // B -> A + if t.Key.TradePairId.MakerDenom == id.Token1 || t.Key.TradePairId.TakerDenom == id.Token0 { + TokenAInReserves = TokenAInReserves.Add(t.ReservesTakerDenom) + TokenBInReserves = TokenBInReserves.Add(t.ReservesMakerDenom) + } + + } + + s.assertDexBalance(id.Token0, TokenAInReserves) + s.assertDexBalance(id.Token1, TokenBInReserves) + +} + +// We assume, if there is a TokenB tranche in dex module, it's always BEL. +func (s *DexStateTestSuite) expectedInOutTokensAmount(tokenA sdk.Coin, denomOut string) (amountOut math.Int) { + pair := dextypes.MustNewPairID(tokenA.Denom, denomOut) + amountOut = math.ZeroInt() + // Active tranches B -> A + tranches, err := s.App.DexKeeper.LimitOrderTrancheAll(s.Ctx, &dextypes.QueryAllLimitOrderTrancheRequest{ + PairId: pair.CanonicalString(), + TokenIn: pair.Token1, + Pagination: nil, + }) + s.Require().NoError(err) + reserveA := tokenA.Amount + + for _, t := range tranches.LimitOrderTranche { + // users tokenA denom = tranche TakerDenom + // t.ReservesMakerDenom - reserve TokenB we are going to get + // t.Price() - price taker -> maker => 1/t.Price() - maker -> taker + // maxSwap - max amount of tokenA (ReservesTakerDenom) tranche can consume us by changing ReservesMakerDenom -> ReservesTakerDenom + maxSwap := math_utils.NewPrecDecFromInt(t.ReservesMakerDenom).Quo(t.Price()).TruncateInt() + // we can swap full our tranche + if maxSwap.GTE(reserveA) { + // expected to get tokenB = tokenA* + amountOut = amountOut.Add(math_utils.NewPrecDecFromInt(reserveA).Mul(t.Price()).TruncateInt()) + reserveA = math.ZeroInt() + break + } + reserveA = reserveA.Sub(maxSwap) + amountOut = amountOut.Add(t.ReservesMakerDenom) + } + return amountOut +} + +func (s *DexStateTestSuite) assertExpectedTrancheKey(initialKey, msgKey string, params placeLimitOrderMakerTestParams) { + // we expect initialKey != msgKey + if params.ExistingTokenAHolders == NoneLO || params.PreexistingTraded || params.OrderType == int32(dextypes.LimitOrderType_GOOD_TIL_TIME) || params.OrderType == int32(dextypes.LimitOrderType_JUST_IN_TIME) { + s.NotEqual(initialKey, msgKey) + return + } + + //otherwise they are equal + s.Equal(initialKey, msgKey) +} + +func TestPlaceLimitOrderMaker(t *testing.T) { + testParams := []testParams{ + {field: "ExistingTokenAHolders", states: []string{NoneLO, CreatorLO, OneOtherLO, OneOtherAndCreatorLO}}, + {field: "BehindEnemyLines", states: []string{BehindEnemyLineNo, BehindEnemyLineLessLimit, BehindEnemyLineGreaterLimit}}, + {field: "PreexistingTraded", states: []string{True, False}}, + {field: "OrderType", states: []string{ + dextypes.LimitOrderType_name[int32(dextypes.LimitOrderType_GOOD_TIL_CANCELLED)], + dextypes.LimitOrderType_name[int32(dextypes.LimitOrderType_GOOD_TIL_TIME)], + dextypes.LimitOrderType_name[int32(dextypes.LimitOrderType_JUST_IN_TIME)], + }}, + } + testCasesRaw := generatePermutations(testParams) + testCases := hydrateAllPlaceLOMakerTestCases(testCasesRaw) + + s := new(DexStateTestSuite) + s.SetT(t) + s.SetupTest() + totalExpectedToSwap := math.ZeroInt() + + for i, tc := range testCases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + s.SetT(t) + tc.printTestInfo(t) + + initialTrancheKey := s.setupLoState(tc) + s.fundCreatorBalanceDefault(tc.PairID) + // + + amountIn := sdk.NewCoin(tc.PairID.Token0, math.NewInt(MakerAmountIn)) + var expTime *time.Time + if tc.OrderType == int32(dextypes.LimitOrderType_GOOD_TIL_TIME) { + // any time is valid for tests + t := time.Now() + expTime = &t + } + expectedSwapTakerDenom := s.expectedInOutTokensAmount(amountIn, tc.PairID.Token1) + totalExpectedToSwap = totalExpectedToSwap.Add(expectedSwapTakerDenom) + resp, err := s.makePlaceLO(s.creator, amountIn, tc.PairID.Token1, DefaultSellPrice, dextypes.LimitOrderType(tc.OrderType), expTime) + s.Require().NoError(err) + + // 1. generic liquidity check assertion + s.assertLiquidity(*tc.PairID) + // 2. BEL assertion + s.intsApproxEqual("", expectedSwapTakerDenom, resp.TakerCoinOut.Amount, 1) + // 3. TrancheKey assertion + s.assertExpectedTrancheKey(initialTrancheKey, resp.TrancheKey, tc) + + }) + } + s.SetT(t) + // sanity check: at least one `expectedSwapTakerDenom` > 0 + s.True(totalExpectedToSwap.GT(math.ZeroInt())) +} diff --git a/tests/dex/state_place_limit_order_taker_test.go b/tests/dex/state_place_limit_order_taker_test.go new file mode 100644 index 000000000..5afbc8347 --- /dev/null +++ b/tests/dex/state_place_limit_order_taker_test.go @@ -0,0 +1,289 @@ +package dex_state_test + +import ( + "cosmossdk.io/math" + "errors" + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + math_utils "github.com/neutron-org/neutron/v4/utils/math" + dextypes "github.com/neutron-org/neutron/v4/x/dex/types" + "strconv" + "strings" + "testing" +) + +// LiquidityType +const ( + LO = "LO" + LP = "LP" + LOLP = "LOLP" +) + +// LimitPrice +const ( + LOWSELLPRICE = "LOWSELLPRICE" + AVGSELLPRICE = "AVGSELLPRICE" + HIGHSELLPRICE = "HIGHSELLPRICE" +) + +var ( + DefaultPriceDelta = math_utils.NewPrecDecWithPrec(1, 1) // 0.1 + DefaultStartPrice = math_utils.NewPrecDecWithPrec(2, 0) // 2.0 +) + +type placeLimitOrderTakerTestParams struct { + PairID *dextypes.PairID + + // State Conditions + LiquidityType string + TicksDistribution []int64 + + // Message Variants + OrderType int32 // FillOrKill or ImmediateOrCancel + AmountIn sdk.Coin + LimitPrice math_utils.PrecDec + MaxAmountOut *math.Int +} + +func (p placeLimitOrderTakerTestParams) printTestInfo(t *testing.T) { + t.Logf(` + LiquidityType: %s + TicksDistribution: %v + OrderType: %v + AmountIn: %v + LimitPrice: %v, + MaxAmountOut: %v`, + p.LiquidityType, + p.TicksDistribution, + dextypes.LimitOrderType_name[p.OrderType], + p.AmountIn, + p.LimitPrice, + p.MaxAmountOut, + ) +} + +func hydratePlaceLOTakerTestCase(params map[string]string, pairID *dextypes.PairID) placeLimitOrderTakerTestParams { + ticks, err := strconv.Atoi(params["TicksDistribution"]) + if err != nil { + panic(err) + } + amountInShare, err := strconv.Atoi(params["AmountIn"]) + if err != nil { + panic(err) + } + // average sell price is defined by loop over the ticks in `setupLoTakerState` + // and ~ ((2+0.1*(ticksAmount-1))+2)/2 = 2+0.05*(ticksAmount-1) + // to buy 100% we want to put ~ BaseTokenAmountInt*(2+0.05*(ticksAmount-1)) as amountIn + avgPrice := DefaultStartPrice.Add( + DefaultPriceDelta.QuoInt64(2).MulInt64(int64(ticks - 1)), + ) + amountIn := avgPrice.MulInt(BaseTokenAmountInt).MulInt64(int64(amountInShare)).QuoInt64(100).TruncateInt() + + maxOutShare, err := strconv.Atoi(params["MaxAmountOut"]) + if err != nil { + panic(err) + } + + var maxAmountOut *math.Int + if maxOutShare > 0 { + maxAmountOut = &math.Int{} + *maxAmountOut = BaseTokenAmountInt.MulRaw(int64(maxOutShare)).QuoRaw(100) + } + + LimitPrice := DefaultStartPrice // LOWSELLPRICE + switch params["LimitPrice"] { + case AVGSELLPRICE: + LimitPrice = avgPrice + case HIGHSELLPRICE: + // 2 * max price + LimitPrice = DefaultStartPrice.Add(DefaultPriceDelta.MulInt64(int64(ticks)).MulInt64Mut(2)) + } + return placeLimitOrderTakerTestParams{ + LiquidityType: params["LiquidityType"], + TicksDistribution: generateTicks(ticks), + OrderType: dextypes.LimitOrderType_value[params["OrderType"]], + AmountIn: sdk.NewCoin(pairID.Token1, amountIn), + MaxAmountOut: maxAmountOut, + LimitPrice: math_utils.OnePrecDec().Quo(LimitPrice.Add(DefaultPriceDelta)), + } +} + +func hydrateAllPlaceLOTakerTestCases(paramsList []map[string]string) []placeLimitOrderTakerTestParams { + allTCs := make([]placeLimitOrderTakerTestParams, 0) + for i, paramsRaw := range paramsList { + pairID := generatePairID(i) + tc := hydratePlaceLOTakerTestCase(paramsRaw, pairID) + tc.PairID = pairID + allTCs = append(allTCs, tc) + } + + return allTCs +} + +func generateTicks(ticksAmount int) []int64 { + ticks := make([]int64, 0, ticksAmount) + for i := 0; i < ticksAmount; i++ { + tick, err := dextypes.CalcTickIndexFromPrice(DefaultStartPrice.Add(DefaultPriceDelta.MulInt64(int64(i)))) + if err != nil { + panic(err) + } + ticks = append(ticks, tick) + } + return ticks +} + +func (s *DexStateTestSuite) setupLoTakerState(params placeLimitOrderTakerTestParams) { + if params.LiquidityType == None { + return + } + coins := sdk.NewCoins(sdk.NewCoin(params.PairID.Token0, BaseTokenAmountInt), sdk.NewCoin(params.PairID.Token1, BaseTokenAmountInt)) + s.FundAcc(s.alice, coins) + // BaseTokenAmountInt is full liquidity + tickLiquidity := BaseTokenAmountInt.QuoRaw(int64(len(params.TicksDistribution))) + if params.LiquidityType == LOLP { + tickLiquidity = tickLiquidity.QuoRaw(2) + } + for _, tick := range params.TicksDistribution { + // hit both if LOLP + if strings.Contains(params.LiquidityType, LO) { + price := dextypes.MustCalcPrice(tick) + amountIn := sdk.NewCoin(params.PairID.Token0, tickLiquidity) + s.makePlaceLOSuccess(s.alice, amountIn, params.PairID.Token1, price.String(), dextypes.LimitOrderType_GOOD_TIL_CANCELLED, nil) + } + if strings.Contains(params.LiquidityType, LP) { + liduidity := LiquidityDistribution{ + TokenA: sdk.NewCoin(params.PairID.Token0, tickLiquidity), + TokenB: sdk.NewCoin(params.PairID.Token1, math.ZeroInt()), + } + // tick+DefaultFee to put liquidity the same tick as LO + resp, err := s.makeDeposit(s.alice, liduidity, DefaultFee, tick+DefaultFee, true) + fmt.Println(resp) + s.NoError(err) + } + } +} + +func MaxAmountAOut(params placeLimitOrderTakerTestParams) math.Int { + // liquidity equally distributed over the ticks with a delta `DefaultPriceDelta` starting from `DefaultStartPrice` + // we find amount of ticks (tickOffset) are being covered by limitPrice + // and that is the max liquidity we can swap in + + if params.LiquidityType == None { + return math.ZeroInt() + } + // see `setupLoTakerState` + tickLiquidity := BaseTokenAmountInt.QuoRaw(int64(len(params.TicksDistribution))) + + tickOffset := math_utils.OnePrecDec().Quo(params.LimitPrice).Sub(DefaultStartPrice).Quo(DefaultPriceDelta).Ceil().TruncateInt() + liquidity := tickLiquidity.Mul(tickOffset) + + return math.MinInt(liquidity, BaseTokenAmountInt) +} + +func ExpectedInOut(params placeLimitOrderTakerTestParams) (math.Int, math.Int) { + if params.LiquidityType == None { + return math.ZeroInt(), math.ZeroInt() + } + LimitTick, err := dextypes.CalcTickIndexFromPrice(math_utils.OnePrecDec().Quo(params.LimitPrice)) + if err != nil { + panic(err) + } + tickLiquidity := BaseTokenAmountInt.QuoRaw(int64(len(params.TicksDistribution))) + TotalIn := math.ZeroInt() + TotalOut := math.ZeroInt() + for _, tick := range params.TicksDistribution { + if LimitTick > tick { + break + } + toOut := tickLiquidity + if params.MaxAmountOut != nil { + toOut = math.MinInt(toOut, (*params.MaxAmountOut).Sub(TotalOut)) + } + + toIn := dextypes.MustCalcPrice(tick).MulInt(toOut).Ceil().TruncateInt() + if toIn.GT(params.AmountIn.Amount.Sub(TotalIn)) { + toIn = params.AmountIn.Amount.Sub(TotalIn) + toOut = dextypes.MustCalcPrice(-1 * tick).MulInt(toIn).Ceil().TruncateInt() + } + TotalIn = TotalIn.Add( + toIn, + ) + TotalOut = TotalOut.Add(toOut) + } + return TotalIn, TotalOut +} + +func (s *DexStateTestSuite) handleTakerErrors(params placeLimitOrderTakerTestParams, err error) { + if params.OrderType == int32(dextypes.LimitOrderType_FILL_OR_KILL) { + maxIn, _ := ExpectedInOut(params) + if maxIn.LT(params.AmountIn.Amount) { + if errors.Is(err, dextypes.ErrFoKLimitOrderNotFilled) { + s.T().Skip() + + } + } + } + s.NoError(err) +} + +func TestPlaceLimitOrderTaker(t *testing.T) { + testParams := []testParams{ + // state + {field: "LiquidityType", states: []string{LO, LP, LOLP}}, + {field: "TicksDistribution", states: []string{"1", "2", "10"}}, // these are not the ticks but the amount of ticks we want to distribute liquidity over + {field: "OrderType", states: []string{ + dextypes.LimitOrderType_name[int32(dextypes.LimitOrderType_FILL_OR_KILL)], + dextypes.LimitOrderType_name[int32(dextypes.LimitOrderType_IMMEDIATE_OR_CANCEL)], + }}, + // msg + {field: "AmountIn", states: []string{FiftyPCT, TwoHundredPct}}, + {field: "MaxAmountOut", states: []string{ZeroPCT, FiftyPCT, HundredPct, TwoHundredPct}}, + {field: "LimitPrice", states: []string{LOWSELLPRICE, AVGSELLPRICE, HIGHSELLPRICE}}, + } + testCasesRaw := generatePermutations(testParams) + testCases := hydrateAllPlaceLOTakerTestCases(testCasesRaw) + + s := new(DexStateTestSuite) + s.SetT(t) + s.SetupTest() + + for i, tc := range testCases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + fmt.Println(testCasesRaw[i]) + s.SetT(t) + tc.printTestInfo(t) + + s.setupLoTakerState(tc) + s.fundCreatorBalanceDefault(tc.PairID) + // + + resp, err := s.makePlaceTakerLO(s.creator, tc.AmountIn, tc.PairID.Token0, tc.LimitPrice.String(), dextypes.LimitOrderType(tc.OrderType), tc.MaxAmountOut) + fmt.Println(resp) + fmt.Println(MaxAmountAOut(tc)) + s.handleTakerErrors(tc, err) + + expIn, expOut := ExpectedInOut(tc) + // TODO: fix rounding issues + s.intsApproxEqual("", expIn, resp.CoinIn.Amount, 10) + s.intsApproxEqual("", expOut, resp.TakerCoinOut.Amount, 10) + + s.True( + tc.LimitPrice.MulInt(resp.CoinIn.Amount).TruncateInt().LTE(resp.TakerCoinOut.Amount), + ) + + if tc.MaxAmountOut != nil { + s.True(resp.TakerCoinOut.Amount.LTE(*tc.MaxAmountOut)) + } + + if tc.OrderType == int32(dextypes.LimitOrderType_FILL_OR_KILL) { + // we should fill either AmountIn or MaxAmountOut + s.Condition(func() bool { + if tc.MaxAmountOut != nil { + return resp.TakerCoinOut.Amount.Sub(*tc.MaxAmountOut).Abs().LTE(math.NewInt(1)) || resp.CoinIn.Amount.Sub(tc.AmountIn.Amount).Abs().LTE(math.NewInt(1)) + } + return resp.CoinIn.Amount.Sub(tc.AmountIn.Amount).Abs().LTE(math.NewInt(1)) + }) + } + }) + } +} diff --git a/tests/dex/state_setup_test.go b/tests/dex/state_setup_test.go index a4588fc50..3e2ea7530 100644 --- a/tests/dex/state_setup_test.go +++ b/tests/dex/state_setup_test.go @@ -1,10 +1,13 @@ package dex_state_test import ( - "fmt" - "cosmossdk.io/math" + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/cosmos/cosmos-sdk/x/bank/types" + "strconv" + "time" "github.com/neutron-org/neutron/v4/testutil/apptesting" "github.com/neutron-org/neutron/v4/testutil/common/sample" @@ -23,9 +26,10 @@ const ( // Percents const ( - ZeroPCT = "0" - FiftyPCT = "50" - HundredPct = "100" + ZeroPCT = "0" + FiftyPCT = "50" + HundredPct = "100" + TwoHundredPct = "200" ) // ExistingShareHolders @@ -54,8 +58,8 @@ const ( // Default Values const ( BaseTokenAmount = 1_000_000 - DefaultTick = 0 - DefaultFee = 1 + DefaultTick = 6932 // 1.0001^6932 ~ 2.00003 + DefaultFee = 200 DefaultStartingBalance = 10_000_000 ) @@ -64,6 +68,29 @@ var ( DefaultStartingBalanceInt = math.NewInt(DefaultStartingBalance) ) +type Balances struct { + Dex sdk.Coins + Creator sdk.Coins + Alice sdk.Coins + Total sdk.Coins +} + +type BalanceDelta struct { + Dex math.Int + Creator math.Int + Alice math.Int + Total math.Int +} + +func BalancesDelta(b1, b2 Balances, denom string) BalanceDelta { + return BalanceDelta{ + Dex: b1.Dex.AmountOf(denom).Sub(b2.Dex.AmountOf(denom)), + Creator: b1.Creator.AmountOf(denom).Sub(b2.Creator.AmountOf(denom)), + Alice: b1.Alice.AmountOf(denom).Sub(b2.Alice.AmountOf(denom)), + Total: b1.Total.AmountOf(denom).Sub(b2.Total.AmountOf(denom)), + } +} + type SharedParams struct { Tick int64 Fee uint64 @@ -121,6 +148,14 @@ func splitLiquidityDistribution(liquidityDistribution LiquidityDistribution, n i // State Parsers ////////////////////////////////////////////////////////////// +func parseInt(v string) int { + i, err := strconv.Atoi(v) + if err != nil { + panic(err) + } + return i +} + func parseBool(b string) bool { switch b { case True: @@ -161,7 +196,48 @@ func parseLiquidityDistribution(liquidityDistribution string, pairID *dextypes.P } // Misc. Helpers ////////////////////////////////////////////////////////////// -func (s *DexStateTestSuite) makeDeposit(addr sdk.AccAddress, depositAmts LiquidityDistribution, disableAutoSwap bool) (*dextypes.MsgDepositResponse, error) { +func (s *DexStateTestSuite) GetBalances() Balances { + var snap Balances + snap.Creator = s.App.BankKeeper.GetAllBalances(s.Ctx, s.creator) + snap.Alice = s.App.BankKeeper.GetAllBalances(s.Ctx, s.alice) + snap.Dex = s.App.BankKeeper.GetAllBalances(s.Ctx, s.App.AccountKeeper.GetModuleAddress("dex")) + resp, err := s.App.BankKeeper.TotalSupply(s.Ctx, &types.QueryTotalSupplyRequest{}) + if err != nil { + panic(err) + } + snap.Total = resp.Supply + var key []byte + if resp.Pagination != nil { + key = resp.Pagination.NextKey + } + for key != nil { + resp, err = s.App.BankKeeper.TotalSupply(s.Ctx, &types.QueryTotalSupplyRequest{ + Pagination: &query.PageRequest{ + Key: key, + Offset: 0, + Limit: 0, + CountTotal: false, + Reverse: false, + }, + }) + if err != nil { + panic(err) + } + snap.Total = snap.Total.Add(resp.Supply...) + if resp.Pagination != nil { + key = resp.Pagination.NextKey + } + } + + return snap +} + +func (s *DexStateTestSuite) makeDepositDefault(addr sdk.AccAddress, depositAmts LiquidityDistribution, disableAutoSwap bool) (*dextypes.MsgDepositResponse, error) { + return s.makeDeposit(addr, depositAmts, DefaultFee, DefaultTick, disableAutoSwap) + +} + +func (s *DexStateTestSuite) makeDeposit(addr sdk.AccAddress, depositAmts LiquidityDistribution, fee uint64, tick int64, disableAutoSwap bool) (*dextypes.MsgDepositResponse, error) { return s.msgServer.Deposit(s.Ctx, &dextypes.MsgDeposit{ Creator: addr.String(), Receiver: addr.String(), @@ -169,21 +245,104 @@ func (s *DexStateTestSuite) makeDeposit(addr sdk.AccAddress, depositAmts Liquidi TokenB: depositAmts.TokenB.Denom, AmountsA: []math.Int{depositAmts.TokenA.Amount}, AmountsB: []math.Int{depositAmts.TokenB.Amount}, - TickIndexesAToB: []int64{DefaultTick}, - Fees: []uint64{DefaultFee}, + TickIndexesAToB: []int64{tick}, + Fees: []uint64{fee}, Options: []*dextypes.DepositOptions{{DisableAutoswap: disableAutoSwap}}, }) } func (s *DexStateTestSuite) makeDepositSuccess(addr sdk.AccAddress, depositAmts LiquidityDistribution, disableAutoSwap bool) *dextypes.MsgDepositResponse { - resp, err := s.makeDeposit(addr, depositAmts, disableAutoSwap) + resp, err := s.makeDepositDefault(addr, depositAmts, disableAutoSwap) + s.NoError(err) + + return resp +} + +func (s *DexStateTestSuite) makeWithdraw(addr sdk.AccAddress, tokenA string, tokenB string, sharesToRemove math.Int) (*dextypes.MsgWithdrawalResponse, error) { + return s.msgServer.Withdrawal(s.Ctx, &dextypes.MsgWithdrawal{ + Creator: addr.String(), + Receiver: addr.String(), + TokenA: tokenA, + TokenB: tokenB, + SharesToRemove: []math.Int{sharesToRemove}, + TickIndexesAToB: []int64{DefaultTick}, + Fees: []uint64{DefaultFee}, + }) +} + +func (s *DexStateTestSuite) makePlaceTakerLO(addr sdk.AccAddress, amountIn sdk.Coin, tokenOut string, sellPrice string, orderType dextypes.LimitOrderType, maxAmountOut *math.Int) (*dextypes.MsgPlaceLimitOrderResponse, error) { + p, err := math_utils.NewPrecDecFromStr(sellPrice) + if err != nil { + panic(err) + } + return s.msgServer.PlaceLimitOrder(s.Ctx, &dextypes.MsgPlaceLimitOrder{ + Creator: addr.String(), + Receiver: addr.String(), + TokenIn: amountIn.Denom, + TokenOut: tokenOut, + TickIndexInToOut: 0, + AmountIn: amountIn.Amount, + OrderType: orderType, + ExpirationTime: nil, + MaxAmountOut: maxAmountOut, + LimitSellPrice: &p, + }) +} + +func (s *DexStateTestSuite) makePlaceLO(addr sdk.AccAddress, amountIn sdk.Coin, tokenOut string, sellPrice string, orderType dextypes.LimitOrderType, expTime *time.Time) (*dextypes.MsgPlaceLimitOrderResponse, error) { + p, err := math_utils.NewPrecDecFromStr(sellPrice) + if err != nil { + panic(err) + } + return s.msgServer.PlaceLimitOrder(s.Ctx, &dextypes.MsgPlaceLimitOrder{ + Creator: addr.String(), + Receiver: addr.String(), + TokenIn: amountIn.Denom, + TokenOut: tokenOut, + TickIndexInToOut: 0, + AmountIn: amountIn.Amount, + OrderType: orderType, + ExpirationTime: expTime, + MaxAmountOut: nil, + LimitSellPrice: &p, + }) +} + +func (s *DexStateTestSuite) makePlaceLOSuccess(addr sdk.AccAddress, amountIn sdk.Coin, tokenOut string, sellPrice string, orderType dextypes.LimitOrderType, expTime *time.Time) *dextypes.MsgPlaceLimitOrderResponse { + resp, err := s.makePlaceLO(addr, amountIn, tokenOut, sellPrice, orderType, expTime) s.NoError(err) + fmt.Println("setup: ", resp) + return resp +} + +func (s *DexStateTestSuite) makeCancel(addr sdk.AccAddress, trancheKey string) (*dextypes.MsgCancelLimitOrderResponse, error) { + return s.msgServer.CancelLimitOrder(s.Ctx, &dextypes.MsgCancelLimitOrder{ + Creator: addr.String(), + TrancheKey: trancheKey, + }) +} +func (s *DexStateTestSuite) makeCancelSuccess(addr sdk.AccAddress, trancheKey string) *dextypes.MsgCancelLimitOrderResponse { + resp, err := s.makeCancel(addr, trancheKey) + s.NoError(err) + return resp +} + +func (s *DexStateTestSuite) makeWithdrawFilled(addr sdk.AccAddress, trancheKey string) (*dextypes.MsgWithdrawFilledLimitOrderResponse, error) { + return s.msgServer.WithdrawFilledLimitOrder(s.Ctx, &dextypes.MsgWithdrawFilledLimitOrder{ + Creator: addr.String(), + TrancheKey: trancheKey, + }) +} + +func (s *DexStateTestSuite) makeWithdrawFilledSuccess(addr sdk.AccAddress, trancheKey string) *dextypes.MsgWithdrawFilledLimitOrderResponse { + resp, err := s.makeWithdrawFilled(addr, trancheKey) + s.NoError(err) return resp } func calcDepositValueAsToken0(tick int64, amount0, amount1 math.Int) math_utils.PrecDec { - price1To0CenterTick := dextypes.MustCalcPrice(tick) + price1To0CenterTick := dextypes.MustCalcPrice(-1 * tick) amount1ValueAsToken0 := price1To0CenterTick.MulInt(amount1) depositValue := amount1ValueAsToken0.Add(math_utils.NewPrecDecFromInt(amount0)) @@ -206,13 +365,18 @@ func (s *DexStateTestSuite) fundCreatorBalanceDefault(pairID *dextypes.PairID) { // Assertions ///////////////////////////////////////////////////////////////// -func (s *DexStateTestSuite) intsEqual(field string, expected, actual math.Int) { - s.True(actual.Equal(expected), "For %v: Expected %v Got %v", field, expected, actual) +func (s *DexStateTestSuite) intsApproxEqual(field string, expected, actual math.Int, absPrecision int64) { + s.True(actual.Sub(expected).Abs().LTE(math.NewInt(absPrecision)), "For %v: Expected %v (+-%d) Got %v)", field, expected, absPrecision, actual) } func (s *DexStateTestSuite) assertBalance(addr sdk.AccAddress, denom string, expected math.Int) { trueBalance := s.App.BankKeeper.GetBalance(s.Ctx, addr, denom) - s.intsEqual(fmt.Sprintf("Balance %s", denom), expected, trueBalance.Amount) + s.intsApproxEqual(fmt.Sprintf("Balance %s", denom), expected, trueBalance.Amount, 1) +} + +func (s *DexStateTestSuite) assertBalanceWithPrecision(addr sdk.AccAddress, denom string, expected math.Int, prec int64) { + trueBalance := s.App.BankKeeper.GetBalance(s.Ctx, addr, denom) + s.intsApproxEqual(fmt.Sprintf("Balance %s", denom), expected, trueBalance.Amount, prec) } func (s *DexStateTestSuite) assertCreatorBalance(denom string, expected math.Int) { @@ -230,8 +394,8 @@ func (s *DexStateTestSuite) assertPoolBalance(pairID *dextypes.PairID, tick int6 reservesA := pool.LowerTick0.ReservesMakerDenom reservesB := pool.UpperTick1.ReservesMakerDenom - s.intsEqual("Pool ReservesA", expectedA, reservesA) - s.intsEqual("Pool ReservesB", expectedB, reservesB) + s.intsApproxEqual("Pool ReservesA", expectedA, reservesA, 1) + s.intsApproxEqual("Pool ReservesB", expectedB, reservesB, 1) } // Core Test Setup //////////////////////////////////////////////////////////// @@ -241,12 +405,14 @@ type DexStateTestSuite struct { msgServer dextypes.MsgServer creator sdk.AccAddress alice sdk.AccAddress + bob sdk.AccAddress } func (s *DexStateTestSuite) SetupTest() { s.Setup() s.creator = sdk.MustAccAddressFromBech32(sample.AccAddress()) s.alice = sdk.MustAccAddressFromBech32(sample.AccAddress()) + s.bob = sdk.MustAccAddressFromBech32(sample.AccAddress()) s.msgServer = dexkeeper.NewMsgServerImpl(s.App.DexKeeper) } diff --git a/tests/dex/state_withdraw_test.go b/tests/dex/state_withdraw_test.go new file mode 100644 index 000000000..ea9518b30 --- /dev/null +++ b/tests/dex/state_withdraw_test.go @@ -0,0 +1,173 @@ +package dex_state_test + +import ( + "fmt" + dextypes "github.com/neutron-org/neutron/v4/x/dex/types" + "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +type withdrawTestParams struct { + // State Conditions + DepositState + // Message Variants + SharesToRemoveAmm int64 +} + +func (p withdrawTestParams) printTestInfo(t *testing.T) { + t.Logf(` + Existing Shareholders: %s + Existing Liquidity Distribution: %v + Shares to remove: %v`, + p.ExistingShareHolders, + p.ExistingLiquidityDistribution, + p.SharesToRemoveAmm, + ) +} + +func (s *DexStateTestSuite) handleWithdrawFailureCases(params withdrawTestParams, err error) { + if params.SharesToRemoveAmm == 0 { + s.ErrorIs(err, dextypes.ErrZeroWithdraw) + } else { + s.NoError(err) + } +} + +func hydrateWithdrawTestCase(params map[string]string, pairID *dextypes.PairID) withdrawTestParams { + existingShareHolders := params["ExistingShareHolders"] + var liquidityDistribution LiquidityDistribution + + if existingShareHolders == None { + liquidityDistribution = parseLiquidityDistribution(TokenA0TokenB0, pairID) + } else { + liquidityDistribution = parseLiquidityDistribution(params["LiquidityDistribution"], pairID) + } + + sharesToRemove, err := strconv.ParseInt(params["SharesToRemoveAmm"], 10, 64) + if err != nil { + panic(fmt.Sprintln("invalid SharesToRemoveAmm", err)) + } + + var valueIncrease LiquidityDistribution + if liquidityDistribution.empty() { + valueIncrease = parseLiquidityDistribution(TokenA0TokenB0, pairID) + } else { + valueIncrease = parseLiquidityDistribution(params["PoolValueIncrease"], pairID) + } + + return withdrawTestParams{ + DepositState: DepositState{ + ExistingShareHolders: existingShareHolders, + ExistingLiquidityDistribution: liquidityDistribution, + SharedParams: DefaultSharedParams, + PoolValueIncrease: valueIncrease, + }, + SharesToRemoveAmm: sharesToRemove, + } +} + +func hydrateAllWithdrawTestCases(paramsList []map[string]string) []withdrawTestParams { + allTCs := make([]withdrawTestParams, 0) + for i, paramsRaw := range paramsList { + pairID := generatePairID(i) + tc := hydrateWithdrawTestCase(paramsRaw, pairID) + tc.PairID = pairID + allTCs = append(allTCs, tc) + } + + return allTCs +} + +func TestWithdraw(t *testing.T) { + testParams := []testParams{ + {field: "ExistingShareHolders", states: []string{Creator, OneOtherAndCreator}}, + {field: "LiquidityDistribution", states: []string{ + TokenA0TokenB1, + TokenA0TokenB2, + TokenA1TokenB0, + TokenA1TokenB1, + TokenA1TokenB2, + TokenA2TokenB0, + TokenA2TokenB1, + TokenA2TokenB2, + }}, + {field: "PoolValueIncrease", states: []string{TokenA0TokenB0}}, + {field: "SharesToRemoveAmm", states: []string{ZeroPCT, FiftyPCT, HundredPct}}, + } + testCasesRaw := generatePermutations(testParams) + testCases := hydrateAllWithdrawTestCases(testCasesRaw) + + s := new(DexStateTestSuite) + s.SetT(t) + s.SetupTest() + + for i, tc := range testCases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + s.SetT(t) + tc.printTestInfo(t) + + s.setupDepositState(tc.DepositState) + s.fundCreatorBalanceDefault(tc.PairID) + // + poolID, found := s.App.DexKeeper.GetPoolIDByParams(s.Ctx, tc.PairID, tc.Tick, tc.Fee) + if tc.ExistingShareHolders == None { + // This is the ID that will be used when the pool is created + poolID = s.App.DexKeeper.GetPoolCount(s.Ctx) + } else { + require.True(t, found, "Pool not found after deposit") + } + poolDenom := dextypes.NewPoolDenom(poolID) + balancesBefore := s.GetBalances() + existingSharesOwned := balancesBefore.Creator.AmountOf(poolDenom) + toWithdraw := existingSharesOwned.MulRaw(tc.SharesToRemoveAmm).QuoRaw(100) + //// Do the actual Withdraw + _, err := s.makeWithdraw( + s.creator, + tc.ExistingLiquidityDistribution.TokenA.Denom, + tc.ExistingLiquidityDistribution.TokenB.Denom, + toWithdraw, + ) + + // Assert new state is correct + s.handleWithdrawFailureCases(tc, err) + + TokenABalanceBefore := balancesBefore.Creator.AmountOf(tc.ExistingLiquidityDistribution.TokenA.Denom) + TokenBBalanceBefore := balancesBefore.Creator.AmountOf(tc.ExistingLiquidityDistribution.TokenB.Denom) + + balancesAfter := s.GetBalances() + TokenABalanceAfter := balancesAfter.Creator.AmountOf(tc.ExistingLiquidityDistribution.TokenA.Denom) + TokenBBalanceAfter := balancesAfter.Creator.AmountOf(tc.ExistingLiquidityDistribution.TokenB.Denom) + // Assertion 1 + // toWithdraw = withdrawnTokenA + withdrawnTokenB*priceTakerToMaker + priceTakerToMaker := dextypes.MustCalcPrice(-1 * tc.Tick) + s.Require().Equal( + toWithdraw, + TokenABalanceAfter.Sub(TokenABalanceBefore).Add( + priceTakerToMaker.MulInt(TokenBBalanceAfter.Sub(TokenBBalanceBefore)).TruncateInt(), + ), + ) + newExistingSharesOwned := balancesAfter.Creator.AmountOf(poolDenom) + // Assertion 2 + // exact amount of shares burned from a `creator` account + s.intsApproxEqual("New shares owned", newExistingSharesOwned, existingSharesOwned.Sub(toWithdraw), 1) + + // Assertion 3 + // exact amount of shares burned not just moved + newExistingSharesTotal := balancesAfter.Total.AmountOf(poolDenom) + existingSharesTotal := balancesBefore.Total.AmountOf(poolDenom) + s.intsApproxEqual("New total shares supply", newExistingSharesTotal, existingSharesTotal.Sub(toWithdraw), 1) + + // Assertion 4 + // Withdrawn ratio equals pool liquidity ratio (dex balance of the tokens) + // Ac/Bc = Ap/Bp => Ac*Bp = Ap*Bc, modified the equation to avoid div operation + balDeltaTokenA := BalancesDelta(balancesAfter, balancesBefore, tc.ExistingLiquidityDistribution.TokenA.Denom) + balDeltaTokenB := BalancesDelta(balancesAfter, balancesBefore, tc.ExistingLiquidityDistribution.TokenB.Denom) + s.intsApproxEqual("", + balDeltaTokenA.Creator.Mul(balancesBefore.Dex.AmountOf(tc.ExistingLiquidityDistribution.TokenB.Denom)), + balDeltaTokenB.Creator.Mul(balancesBefore.Dex.AmountOf(tc.ExistingLiquidityDistribution.TokenA.Denom)), + 1, + ) + }) + } +} diff --git a/tests/dex/state_withdrawlimitorder_test.go b/tests/dex/state_withdrawlimitorder_test.go new file mode 100644 index 000000000..6306f4e26 --- /dev/null +++ b/tests/dex/state_withdrawlimitorder_test.go @@ -0,0 +1,249 @@ +package dex_state_test + +import ( + "cosmossdk.io/math" + "errors" + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + math_utils "github.com/neutron-org/neutron/v4/utils/math" + dextypes "github.com/neutron-org/neutron/v4/x/dex/types" + "strconv" + "testing" + "time" +) + +type withdrawLimitOrderTestParams struct { + // State Conditions + SharedParams + ExistingTokenAHolders string + Filled int + WithdrawnCreator bool + WithdrawnOneOther bool + Expired bool + OrderType int32 // JIT, GTT, GTC +} + +func (p withdrawLimitOrderTestParams) printTestInfo(t *testing.T) { + t.Logf(` + Existing Shareholders: %s + Filled: %v + WithdrawnCreator: %v + WithdrawnOneOther: %t + Expired: %t + OrderType: %v`, + p.ExistingTokenAHolders, + p.Filled, + // Two fields define a state with a pre-withdrawn tranche + p.WithdrawnCreator, + p.WithdrawnOneOther, + + p.Expired, + p.OrderType, + ) +} + +func hydrateWithdrawLoTestCase(params map[string]string) withdrawLimitOrderTestParams { + selltick, err := dextypes.CalcTickIndexFromPrice(math_utils.MustNewPrecDecFromStr(DefaultSellPrice)) + if err != nil { + panic(err) + } + w := withdrawLimitOrderTestParams{ + ExistingTokenAHolders: params["ExistingTokenAHolders"], + Filled: parseInt(params["Filled"]), + WithdrawnCreator: parseBool(params["WithdrawnCreator"]), + WithdrawnOneOther: parseBool(params["WithdrawnOneOther"]), + Expired: parseBool(params["Expired"]), + OrderType: dextypes.LimitOrderType_value[params["OrderType"]], + } + w.SharedParams.Tick = selltick + return w +} + +func (s *DexStateTestSuite) setupWithdrawLimitOrderTest(params withdrawLimitOrderTestParams) *dextypes.LimitOrderTranche { + coinA := sdk.NewCoin(params.PairID.Token0, BaseTokenAmountInt) + coinB := sdk.NewCoin(params.PairID.Token1, BaseTokenAmountInt.MulRaw(10)) + s.FundAcc(s.creator, sdk.NewCoins(coinA)) + var expTime *time.Time + if params.OrderType == int32(dextypes.LimitOrderType_GOOD_TIL_TIME) { + t := time.Now() + expTime = &t + } + res := s.makePlaceLOSuccess(s.creator, coinA, coinB.Denom, DefaultSellPrice, dextypes.LimitOrderType(params.OrderType), expTime) + + totalDeposited := BaseTokenAmountInt + if params.ExistingTokenAHolders == OneOtherAndCreatorLO { + totalDeposited = totalDeposited.MulRaw(2) + s.FundAcc(s.alice, sdk.NewCoins(coinA)) + s.makePlaceLOSuccess(s.alice, coinA, coinB.Denom, DefaultSellPrice, dextypes.LimitOrderType(params.OrderType), expTime) + } + + // withdraw in two steps: before and after pre-withdraw (if there are any) + halfAmount := totalDeposited.MulRaw(int64(params.Filled)).QuoRaw(2 * 100) + s.FundAcc(s.bob, sdk.NewCoins(coinB).MulInt(math.NewInt(10))) + if params.Filled > 0 { + _, err := s.makePlaceTakerLO(s.bob, coinB, coinA.Denom, DefaultBuyPriceTaker, dextypes.LimitOrderType_IMMEDIATE_OR_CANCEL, &halfAmount) + s.NoError(err) + } + + if params.WithdrawnCreator { + s.makeWithdrawFilledSuccess(s.creator, res.TrancheKey) + } + + if params.WithdrawnOneOther { + s.makeWithdrawFilledSuccess(s.alice, res.TrancheKey) + } + + if params.Filled > 0 { + _, err := s.makePlaceTakerLO(s.bob, coinB, coinA.Denom, DefaultBuyPriceTaker, dextypes.LimitOrderType_IMMEDIATE_OR_CANCEL, &halfAmount) + s.NoError(err) + } + + if params.Expired { + s.App.DexKeeper.PurgeExpiredLimitOrders(s.Ctx, time.Now()) + } + tick, err := dextypes.CalcTickIndexFromPrice(DefaultStartPrice) + s.NoError(err) + tranches, _ := s.App.DexKeeper.LimitOrderTrancheAll(s.Ctx, &dextypes.QueryAllLimitOrderTrancheRequest{ + PairId: params.PairID.CanonicalString(), + TokenIn: params.PairID.Token0, + Pagination: nil, + }) + fmt.Println(tranches) + req := dextypes.QueryGetLimitOrderTrancheRequest{ + PairId: params.PairID.CanonicalString(), + TickIndex: -1 * tick, + TokenIn: params.PairID.Token0, + TrancheKey: res.TrancheKey, + } + fmt.Println(req) + tranchResp, err := s.App.DexKeeper.LimitOrderTranche(s.Ctx, &req) + s.NoError(err) + + ut, _ := s.App.DexKeeper.GetLimitOrderTrancheUser(s.Ctx, s.creator.String(), tranchResp.LimitOrderTranche.Key.TrancheKey) + fmt.Println("user tranche1: ", ut) + return tranchResp.LimitOrderTranche +} + +func hydrateAllWithdrawLoTestCases(paramsList []map[string]string) []withdrawLimitOrderTestParams { + allTCs := make([]withdrawLimitOrderTestParams, 0) + for i, paramsRaw := range paramsList { + pairID := generatePairID(i) + tc := hydrateWithdrawLoTestCase(paramsRaw) + tc.PairID = pairID + allTCs = append(allTCs, tc) + } + + //return allTCs + return removeRedundantWithdrawLOTests(allTCs) +} + +func removeRedundantWithdrawLOTests(params []withdrawLimitOrderTestParams) []withdrawLimitOrderTestParams { + newParams := make([]withdrawLimitOrderTestParams, 0) + for _, p := range params { + // it's impossible to withdraw 0 filled + if p.Filled == 0 && (p.WithdrawnOneOther || p.WithdrawnCreator) { + continue + } + if p.Expired && p.OrderType == int32(dextypes.LimitOrderType_GOOD_TIL_CANCELLED) { + continue + } + if p.WithdrawnOneOther && p.ExistingTokenAHolders == CreatorLO { + continue + } + if p.ExistingTokenAHolders == OneOtherAndCreatorLO && p.OrderType != int32(dextypes.LimitOrderType_GOOD_TIL_CANCELLED) { + // user tranches combined into tranches only for LimitOrderType_GOOD_TIL_CANCELLED + // it does not make any sense to create two tranches + continue + } + newParams = append(newParams, p) + } + return newParams +} + +func (s *DexStateTestSuite) handleWithdrawLimitOrderErrors(params withdrawLimitOrderTestParams, err error) { + if params.Filled == 0 { + if errors.Is(dextypes.ErrWithdrawEmptyLimitOrder, err) { + s.T().Skip() + } + } + s.NoError(err) +} + +func (s *DexStateTestSuite) assertWithdrawFilledAmount(params withdrawLimitOrderTestParams, trancheKey string) { + depositSize := BaseTokenAmountInt + + // expected balance: InitialBalance - depositSize + pre-withdrawn (filled/2 or 0) + withdrawn (filled/2 or filled) + // pre-withdrawn (filled/2 or 0) + withdrawn (filled/2 or filled) === filled + // converted to TokenB + price := dextypes.MustCalcPrice(params.Tick) + expectedBalanceB := price.MulInt(depositSize.MulRaw(int64(params.Filled)).QuoRaw(100)).Ceil().TruncateInt() + expectedBalanceA := depositSize.Sub(depositSize.MulRaw(int64(params.Filled)).QuoRaw(100)) + // 1 - withdrawn amount + s.assertBalanceWithPrecision(s.creator, params.PairID.Token1, expectedBalanceB, 3) + + ut, found := s.App.DexKeeper.GetLimitOrderTrancheUser(s.Ctx, s.creator.String(), trancheKey) + if params.Expired { + // "canceled" amount + s.assertBalance(s.creator, params.PairID.Token0, expectedBalanceA) + s.False(found) + } else { + s.assertBalance(s.creator, params.PairID.Token0, math.ZeroInt()) + if params.Filled == 100 { + s.False(found) + } else { + s.True(found) + s.intsApproxEqual("", expectedBalanceA, ut.SharesOwned.Sub(ut.SharesWithdrawn), 1) + } + } +} + +func TestWithdrawLimitOrder(t *testing.T) { + testParams := []testParams{ + {field: "ExistingTokenAHolders", states: []string{CreatorLO, OneOtherAndCreatorLO}}, + {field: "Filled", states: []string{ZeroPCT, FiftyPCT, HundredPct}}, + {field: "WithdrawnCreator", states: []string{True, False}}, + {field: "WithdrawnOneOther", states: []string{True, False}}, + {field: "OrderType", states: []string{ + dextypes.LimitOrderType_name[int32(dextypes.LimitOrderType_GOOD_TIL_CANCELLED)], + dextypes.LimitOrderType_name[int32(dextypes.LimitOrderType_GOOD_TIL_TIME)], + dextypes.LimitOrderType_name[int32(dextypes.LimitOrderType_JUST_IN_TIME)], + }}, + {field: "Expired", states: []string{True, False}}, + } + testCasesRaw := generatePermutations(testParams) + testCases := hydrateAllWithdrawLoTestCases(testCasesRaw) + + s := new(DexStateTestSuite) + s.SetT(t) + s.SetupTest() + //totalExpectedToSwap := math.ZeroInt() + + for i, tc := range testCases { + //if i != 30 { + // continue + //} + t.Run(strconv.Itoa(i), func(t *testing.T) { + s.SetT(t) + tc.printTestInfo(t) + + initialTrancheKey := s.setupWithdrawLimitOrderTest(tc) + fmt.Println(initialTrancheKey) + + resp, err := s.makeWithdrawFilled(s.creator, initialTrancheKey.Key.TrancheKey) + s.handleWithdrawLimitOrderErrors(tc, err) + fmt.Println("resp", resp) + fmt.Println("err", err) + s.assertWithdrawFilledAmount(tc, initialTrancheKey.Key.TrancheKey) + /* + 3. Assertions + 1. (Value returned + remaining LO value)/ValueIn ~= LimitPrice + 2. TakerDenom withdrawn == userOwnershipRatio * fillPercentage * takerReserves + 3. If expired + 1. MakerDenom withdraw == userOwnershipRatio * fillPercentage * makerReserves + */ + }) + } + //s.SetT(t) + //// check at least one `expectedSwapTakerDenom` > 0 + //s.True(totalExpectedToSwap.GT(math.ZeroInt())) +} From 7b5c777a77411010274f8ce570abb1d466e21deb Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Fri, 6 Sep 2024 13:23:06 -0400 Subject: [PATCH 11/21] Add case to handle negative withdraw amounts --- .../integration_cancellimitorder_test.go | 18 ++++++++++++++++++ x/dex/types/limit_order_tranche.go | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/x/dex/keeper/integration_cancellimitorder_test.go b/x/dex/keeper/integration_cancellimitorder_test.go index 9c12b8d0f..399017deb 100644 --- a/x/dex/keeper/integration_cancellimitorder_test.go +++ b/x/dex/keeper/integration_cancellimitorder_test.go @@ -200,6 +200,24 @@ func (s *DexTestSuite) TestCancelPartiallyFilled() { s.Assert().False(found) } +func (s *DexTestSuite) TestCancelWithdrawThenCancel() { + s.fundAliceBalances(10, 0) + s.fundBobBalances(0, 20) + + // GIVEN alice limit sells 10 TokenA + trancheKey := s.aliceLimitSells("TokenA", -6931, 10) + // Bob swaps some TokenB for 5 TokenA + s.bobLimitSellsWithMaxOut("TokenB", 7000, 20, 5) + + // WHEN alice withdraws + s.aliceWithdrawsLimitSell(trancheKey) + s.assertAliceBalancesInt(sdkmath.ZeroInt(), sdkmath.NewInt(9999181)) + + // THEN Alice cancel still works + s.aliceCancelsLimitSell(trancheKey) + s.assertAliceBalancesInt(sdkmath.NewInt(4999999), sdkmath.NewInt(9999181)) +} + func (s *DexTestSuite) TestCancelPartiallyFilledWithdrawFails() { s.fundAliceBalances(50, 0) s.fundBobBalances(0, 10) diff --git a/x/dex/types/limit_order_tranche.go b/x/dex/types/limit_order_tranche.go index faea72e3d..f23481e5f 100644 --- a/x/dex/types/limit_order_tranche.go +++ b/x/dex/types/limit_order_tranche.go @@ -147,6 +147,11 @@ func (t *LimitOrderTranche) CalcWithdrawAmount(trancheUser *LimitOrderTrancheUse ratioFilled := t.RatioFilled() maxAllowedToWithdraw := ratioFilled.MulInt(trancheUser.SharesOwned) sharesToWithdrawDec := maxAllowedToWithdraw.Sub(math_utils.NewPrecDecFromInt(trancheUser.SharesWithdrawn)) + + // Given rounding it is possible for sharesToWithdrawn > maxAllowedToWithdraw. In this case we just exit. + if !sharesToWithdrawDec.IsPositive() { + return math.ZeroInt(), math.ZeroInt() + } amountOutTokenOutDec := sharesToWithdrawDec.Quo(t.PriceTakerToMaker) // Round shares withdrawn up and amountOut down to ensure math favors dex From 31ae53f3723d9a93a567e858f3d205f982456b8d Mon Sep 17 00:00:00 2001 From: swelf Date: Mon, 9 Sep 2024 18:03:29 +0300 Subject: [PATCH 12/21] another assertion --- tests/dex/state_cancel_test.go | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/tests/dex/state_cancel_test.go b/tests/dex/state_cancel_test.go index 4609c518b..7b9497cb6 100644 --- a/tests/dex/state_cancel_test.go +++ b/tests/dex/state_cancel_test.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + math_utils "github.com/neutron-org/neutron/v4/utils/math" dextypes "github.com/neutron-org/neutron/v4/x/dex/types" "strconv" "testing" @@ -40,7 +41,11 @@ func (p cancelLimitOrderTestParams) printTestInfo(t *testing.T) { } func hydrateCancelLoTestCase(params map[string]string) cancelLimitOrderTestParams { - return cancelLimitOrderTestParams{ + selltick, err := dextypes.CalcTickIndexFromPrice(math_utils.MustNewPrecDecFromStr(DefaultSellPrice)) + if err != nil { + panic(err) + } + c := cancelLimitOrderTestParams{ ExistingTokenAHolders: params["ExistingTokenAHolders"], Filled: parseInt(params["Filled"]), WithdrawnCreator: parseBool(params["WithdrawnCreator"]), @@ -48,11 +53,13 @@ func hydrateCancelLoTestCase(params map[string]string) cancelLimitOrderTestParam Expired: parseBool(params["Expired"]), OrderType: dextypes.LimitOrderType_value[params["OrderType"]], } + c.SharedParams.Tick = selltick + return c } func (s *DexStateTestSuite) setupCancelTest(params cancelLimitOrderTestParams) *dextypes.LimitOrderTranche { coinA := sdk.NewCoin(params.PairID.Token0, BaseTokenAmountInt) - coinB := sdk.NewCoin(params.PairID.Token1, BaseTokenAmountInt) + coinB := sdk.NewCoin(params.PairID.Token1, BaseTokenAmountInt.MulRaw(10)) s.FundAcc(s.creator, sdk.NewCoins(coinA)) var expTime *time.Time if params.OrderType == int32(dextypes.LimitOrderType_GOOD_TIL_TIME) { @@ -144,7 +151,7 @@ func removeRedundantCancelLOTests(params []cancelLimitOrderTestParams) []cancelL } func (s *DexStateTestSuite) handleCancelErrors(params cancelLimitOrderTestParams, err error) { - if params.Expired { + if params.Expired || params.Filled == 100 { if errors.Is(dextypes.ErrActiveLimitOrderNotFound, err) { s.T().Skip() } @@ -152,6 +159,22 @@ func (s *DexStateTestSuite) handleCancelErrors(params cancelLimitOrderTestParams s.NoError(err) } +func (s *DexStateTestSuite) assertCalcelAmount(params cancelLimitOrderTestParams, ut *dextypes.LimitOrderTrancheUser) { + depositSize := BaseTokenAmountInt + + // expected balance: InitialBalance - depositSize + pre-withdrawn (filled/2 or 0) + withdrawn (filled/2 or filled) + // pre-withdrawn (filled/2 or 0) + withdrawn (filled/2 or filled) === filled + // converted to TokenB + price := dextypes.MustCalcPrice(params.Tick) + expectedBalanceB := price.MulInt(depositSize.MulRaw(int64(params.Filled)).QuoRaw(100)).Ceil().TruncateInt() + expectedBalanceA := depositSize.Sub(depositSize.MulRaw(int64(params.Filled)).QuoRaw(100)) + // 1 - withdrawn amount + s.assertBalanceWithPrecision(s.creator, params.PairID.Token1, expectedBalanceB, 3) + + //s.assertBalance(s.creator, params.PairID.Token0, ut.SharesOwned.Sub(ut.SharesWithdrawn)) + s.assertBalance(s.creator, params.PairID.Token0, expectedBalanceA) +} + func TestCancel(t *testing.T) { testParams := []testParams{ {field: "ExistingTokenAHolders", states: []string{CreatorLO, OneOtherAndCreatorLO}}, @@ -179,12 +202,15 @@ func TestCancel(t *testing.T) { initialTrancheKey := s.setupCancelTest(tc) fmt.Println(initialTrancheKey) + ut, _ := s.App.DexKeeper.GetLimitOrderTrancheUser(s.Ctx, s.creator.String(), initialTrancheKey.Key.TrancheKey) resp, err := s.makeCancel(s.creator, initialTrancheKey.Key.TrancheKey) s.handleCancelErrors(tc, err) + _, found := s.App.DexKeeper.GetLimitOrderTrancheUser(s.Ctx, s.creator.String(), initialTrancheKey.Key.TrancheKey) + s.False(found) fmt.Println("resp", resp) fmt.Println("err", err) - + s.assertCalcelAmount(tc, ut) }) } } From 7865c59bc6542571494057a6bbcf3f9d6818b1b2 Mon Sep 17 00:00:00 2001 From: swelf Date: Mon, 9 Sep 2024 18:23:41 +0300 Subject: [PATCH 13/21] linter --- tests/dex/state_cancel_test.go | 31 +++++++------------ .../dex/state_place_limit_order_maker_test.go | 13 ++++---- .../dex/state_place_limit_order_taker_test.go | 22 ++++++------- tests/dex/state_setup_test.go | 27 +++++++--------- tests/dex/state_withdraw_test.go | 8 +++-- tests/dex/state_withdrawlimitorder_test.go | 31 ++++++++----------- 6 files changed, 57 insertions(+), 75 deletions(-) diff --git a/tests/dex/state_cancel_test.go b/tests/dex/state_cancel_test.go index 7b9497cb6..02775f0c8 100644 --- a/tests/dex/state_cancel_test.go +++ b/tests/dex/state_cancel_test.go @@ -1,15 +1,17 @@ package dex_state_test import ( - "cosmossdk.io/math" "errors" - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" - math_utils "github.com/neutron-org/neutron/v4/utils/math" - dextypes "github.com/neutron-org/neutron/v4/x/dex/types" "strconv" "testing" "time" + + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + math_utils "github.com/neutron-org/neutron/v4/utils/math" + dextypes "github.com/neutron-org/neutron/v4/x/dex/types" ) type cancelLimitOrderTestParams struct { @@ -95,19 +97,13 @@ func (s *DexStateTestSuite) setupCancelTest(params cancelLimitOrderTestParams) * } tick, err := dextypes.CalcTickIndexFromPrice(DefaultStartPrice) s.NoError(err) - tranches, _ := s.App.DexKeeper.LimitOrderTrancheAll(s.Ctx, &dextypes.QueryAllLimitOrderTrancheRequest{ - PairId: params.PairID.CanonicalString(), - TokenIn: params.PairID.Token0, - Pagination: nil, - }) - fmt.Println(tranches) + req := dextypes.QueryGetLimitOrderTrancheRequest{ PairId: params.PairID.CanonicalString(), TickIndex: -1 * tick, TokenIn: params.PairID.Token0, TrancheKey: res.TrancheKey, } - fmt.Println(req) tranchResp, err := s.App.DexKeeper.LimitOrderTranche(s.Ctx, &req) s.NoError(err) @@ -159,7 +155,7 @@ func (s *DexStateTestSuite) handleCancelErrors(params cancelLimitOrderTestParams s.NoError(err) } -func (s *DexStateTestSuite) assertCalcelAmount(params cancelLimitOrderTestParams, ut *dextypes.LimitOrderTrancheUser) { +func (s *DexStateTestSuite) assertCalcelAmount(params cancelLimitOrderTestParams) { depositSize := BaseTokenAmountInt // expected balance: InitialBalance - depositSize + pre-withdrawn (filled/2 or 0) + withdrawn (filled/2 or filled) @@ -171,7 +167,6 @@ func (s *DexStateTestSuite) assertCalcelAmount(params cancelLimitOrderTestParams // 1 - withdrawn amount s.assertBalanceWithPrecision(s.creator, params.PairID.Token1, expectedBalanceB, 3) - //s.assertBalance(s.creator, params.PairID.Token0, ut.SharesOwned.Sub(ut.SharesWithdrawn)) s.assertBalance(s.creator, params.PairID.Token0, expectedBalanceA) } @@ -201,16 +196,12 @@ func TestCancel(t *testing.T) { tc.printTestInfo(t) initialTrancheKey := s.setupCancelTest(tc) - fmt.Println(initialTrancheKey) - ut, _ := s.App.DexKeeper.GetLimitOrderTrancheUser(s.Ctx, s.creator.String(), initialTrancheKey.Key.TrancheKey) - resp, err := s.makeCancel(s.creator, initialTrancheKey.Key.TrancheKey) + _, err := s.makeCancel(s.creator, initialTrancheKey.Key.TrancheKey) s.handleCancelErrors(tc, err) _, found := s.App.DexKeeper.GetLimitOrderTrancheUser(s.Ctx, s.creator.String(), initialTrancheKey.Key.TrancheKey) s.False(found) - fmt.Println("resp", resp) - fmt.Println("err", err) - s.assertCalcelAmount(tc, ut) + s.assertCalcelAmount(tc) }) } } diff --git a/tests/dex/state_place_limit_order_maker_test.go b/tests/dex/state_place_limit_order_maker_test.go index 6a7fb8d5d..34afd7e4b 100644 --- a/tests/dex/state_place_limit_order_maker_test.go +++ b/tests/dex/state_place_limit_order_maker_test.go @@ -1,13 +1,15 @@ package dex_state_test import ( + "strconv" + "testing" + "time" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + math_utils "github.com/neutron-org/neutron/v4/utils/math" dextypes "github.com/neutron-org/neutron/v4/x/dex/types" - "strconv" - "testing" - "time" ) // ExistingTokenAHolders @@ -207,7 +209,6 @@ func (s *DexStateTestSuite) assertLiquidity(id dextypes.PairID) { s.assertDexBalance(id.Token0, TokenAInReserves) s.assertDexBalance(id.Token1, TokenBInReserves) - } // We assume, if there is a TokenB tranche in dex module, it's always BEL. @@ -233,7 +234,6 @@ func (s *DexStateTestSuite) expectedInOutTokensAmount(tokenA sdk.Coin, denomOut if maxSwap.GTE(reserveA) { // expected to get tokenB = tokenA* amountOut = amountOut.Add(math_utils.NewPrecDecFromInt(reserveA).Mul(t.Price()).TruncateInt()) - reserveA = math.ZeroInt() break } reserveA = reserveA.Sub(maxSwap) @@ -249,7 +249,7 @@ func (s *DexStateTestSuite) assertExpectedTrancheKey(initialKey, msgKey string, return } - //otherwise they are equal + // otherwise they are equal s.Equal(initialKey, msgKey) } @@ -299,7 +299,6 @@ func TestPlaceLimitOrderMaker(t *testing.T) { s.intsApproxEqual("", expectedSwapTakerDenom, resp.TakerCoinOut.Amount, 1) // 3. TrancheKey assertion s.assertExpectedTrancheKey(initialTrancheKey, resp.TrancheKey, tc) - }) } s.SetT(t) diff --git a/tests/dex/state_place_limit_order_taker_test.go b/tests/dex/state_place_limit_order_taker_test.go index 5afbc8347..fee669c47 100644 --- a/tests/dex/state_place_limit_order_taker_test.go +++ b/tests/dex/state_place_limit_order_taker_test.go @@ -1,15 +1,17 @@ package dex_state_test import ( - "cosmossdk.io/math" "errors" - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" - math_utils "github.com/neutron-org/neutron/v4/utils/math" - dextypes "github.com/neutron-org/neutron/v4/x/dex/types" "strconv" "strings" "testing" + + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + math_utils "github.com/neutron-org/neutron/v4/utils/math" + dextypes "github.com/neutron-org/neutron/v4/x/dex/types" ) // LiquidityType @@ -156,8 +158,7 @@ func (s *DexStateTestSuite) setupLoTakerState(params placeLimitOrderTakerTestPar TokenB: sdk.NewCoin(params.PairID.Token1, math.ZeroInt()), } // tick+DefaultFee to put liquidity the same tick as LO - resp, err := s.makeDeposit(s.alice, liduidity, DefaultFee, tick+DefaultFee, true) - fmt.Println(resp) + _, err := s.makeDeposit(s.alice, liduidity, DefaultFee, tick+DefaultFee, true) s.NoError(err) } } @@ -197,7 +198,7 @@ func ExpectedInOut(params placeLimitOrderTakerTestParams) (math.Int, math.Int) { } toOut := tickLiquidity if params.MaxAmountOut != nil { - toOut = math.MinInt(toOut, (*params.MaxAmountOut).Sub(TotalOut)) + toOut = math.MinInt(toOut, params.MaxAmountOut.Sub(TotalOut)) } toIn := dextypes.MustCalcPrice(tick).MulInt(toOut).Ceil().TruncateInt() @@ -219,7 +220,6 @@ func (s *DexStateTestSuite) handleTakerErrors(params placeLimitOrderTakerTestPar if maxIn.LT(params.AmountIn.Amount) { if errors.Is(err, dextypes.ErrFoKLimitOrderNotFilled) { s.T().Skip() - } } } @@ -249,7 +249,6 @@ func TestPlaceLimitOrderTaker(t *testing.T) { for i, tc := range testCases { t.Run(strconv.Itoa(i), func(t *testing.T) { - fmt.Println(testCasesRaw[i]) s.SetT(t) tc.printTestInfo(t) @@ -258,8 +257,7 @@ func TestPlaceLimitOrderTaker(t *testing.T) { // resp, err := s.makePlaceTakerLO(s.creator, tc.AmountIn, tc.PairID.Token0, tc.LimitPrice.String(), dextypes.LimitOrderType(tc.OrderType), tc.MaxAmountOut) - fmt.Println(resp) - fmt.Println(MaxAmountAOut(tc)) + s.handleTakerErrors(tc, err) expIn, expOut := ExpectedInOut(tc) diff --git a/tests/dex/state_setup_test.go b/tests/dex/state_setup_test.go index 3e2ea7530..875daf0ef 100644 --- a/tests/dex/state_setup_test.go +++ b/tests/dex/state_setup_test.go @@ -1,13 +1,15 @@ package dex_state_test import ( - "cosmossdk.io/math" "fmt" + "strconv" + "time" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "github.com/cosmos/cosmos-sdk/x/bank/types" - "strconv" - "time" "github.com/neutron-org/neutron/v4/testutil/apptesting" "github.com/neutron-org/neutron/v4/testutil/common/sample" @@ -110,6 +112,7 @@ type LiquidityDistribution struct { TokenB sdk.Coin } +//nolint:unused func (l LiquidityDistribution) doubleSided() bool { return l.TokenA.Amount.IsPositive() && l.TokenB.Amount.IsPositive() } @@ -118,6 +121,7 @@ func (l LiquidityDistribution) empty() bool { return l.TokenA.Amount.IsZero() && l.TokenB.Amount.IsZero() } +//nolint:unused func (l LiquidityDistribution) singleSided() bool { return !l.doubleSided() && !l.empty() } @@ -234,7 +238,6 @@ func (s *DexStateTestSuite) GetBalances() Balances { func (s *DexStateTestSuite) makeDepositDefault(addr sdk.AccAddress, depositAmts LiquidityDistribution, disableAutoSwap bool) (*dextypes.MsgDepositResponse, error) { return s.makeDeposit(addr, depositAmts, DefaultFee, DefaultTick, disableAutoSwap) - } func (s *DexStateTestSuite) makeDeposit(addr sdk.AccAddress, depositAmts LiquidityDistribution, fee uint64, tick int64, disableAutoSwap bool) (*dextypes.MsgDepositResponse, error) { @@ -251,6 +254,7 @@ func (s *DexStateTestSuite) makeDeposit(addr sdk.AccAddress, depositAmts Liquidi }) } +//nolint:unparam func (s *DexStateTestSuite) makeDepositSuccess(addr sdk.AccAddress, depositAmts LiquidityDistribution, disableAutoSwap bool) *dextypes.MsgDepositResponse { resp, err := s.makeDepositDefault(addr, depositAmts, disableAutoSwap) s.NoError(err) @@ -258,7 +262,7 @@ func (s *DexStateTestSuite) makeDepositSuccess(addr sdk.AccAddress, depositAmts return resp } -func (s *DexStateTestSuite) makeWithdraw(addr sdk.AccAddress, tokenA string, tokenB string, sharesToRemove math.Int) (*dextypes.MsgWithdrawalResponse, error) { +func (s *DexStateTestSuite) makeWithdraw(addr sdk.AccAddress, tokenA, tokenB string, sharesToRemove math.Int) (*dextypes.MsgWithdrawalResponse, error) { return s.msgServer.Withdrawal(s.Ctx, &dextypes.MsgWithdrawal{ Creator: addr.String(), Receiver: addr.String(), @@ -270,7 +274,7 @@ func (s *DexStateTestSuite) makeWithdraw(addr sdk.AccAddress, tokenA string, tok }) } -func (s *DexStateTestSuite) makePlaceTakerLO(addr sdk.AccAddress, amountIn sdk.Coin, tokenOut string, sellPrice string, orderType dextypes.LimitOrderType, maxAmountOut *math.Int) (*dextypes.MsgPlaceLimitOrderResponse, error) { +func (s *DexStateTestSuite) makePlaceTakerLO(addr sdk.AccAddress, amountIn sdk.Coin, tokenOut, sellPrice string, orderType dextypes.LimitOrderType, maxAmountOut *math.Int) (*dextypes.MsgPlaceLimitOrderResponse, error) { p, err := math_utils.NewPrecDecFromStr(sellPrice) if err != nil { panic(err) @@ -289,7 +293,7 @@ func (s *DexStateTestSuite) makePlaceTakerLO(addr sdk.AccAddress, amountIn sdk.C }) } -func (s *DexStateTestSuite) makePlaceLO(addr sdk.AccAddress, amountIn sdk.Coin, tokenOut string, sellPrice string, orderType dextypes.LimitOrderType, expTime *time.Time) (*dextypes.MsgPlaceLimitOrderResponse, error) { +func (s *DexStateTestSuite) makePlaceLO(addr sdk.AccAddress, amountIn sdk.Coin, tokenOut, sellPrice string, orderType dextypes.LimitOrderType, expTime *time.Time) (*dextypes.MsgPlaceLimitOrderResponse, error) { p, err := math_utils.NewPrecDecFromStr(sellPrice) if err != nil { panic(err) @@ -308,10 +312,9 @@ func (s *DexStateTestSuite) makePlaceLO(addr sdk.AccAddress, amountIn sdk.Coin, }) } -func (s *DexStateTestSuite) makePlaceLOSuccess(addr sdk.AccAddress, amountIn sdk.Coin, tokenOut string, sellPrice string, orderType dextypes.LimitOrderType, expTime *time.Time) *dextypes.MsgPlaceLimitOrderResponse { +func (s *DexStateTestSuite) makePlaceLOSuccess(addr sdk.AccAddress, amountIn sdk.Coin, tokenOut, sellPrice string, orderType dextypes.LimitOrderType, expTime *time.Time) *dextypes.MsgPlaceLimitOrderResponse { resp, err := s.makePlaceLO(addr, amountIn, tokenOut, sellPrice, orderType, expTime) s.NoError(err) - fmt.Println("setup: ", resp) return resp } @@ -322,12 +325,6 @@ func (s *DexStateTestSuite) makeCancel(addr sdk.AccAddress, trancheKey string) ( }) } -func (s *DexStateTestSuite) makeCancelSuccess(addr sdk.AccAddress, trancheKey string) *dextypes.MsgCancelLimitOrderResponse { - resp, err := s.makeCancel(addr, trancheKey) - s.NoError(err) - return resp -} - func (s *DexStateTestSuite) makeWithdrawFilled(addr sdk.AccAddress, trancheKey string) (*dextypes.MsgWithdrawFilledLimitOrderResponse, error) { return s.msgServer.WithdrawFilledLimitOrder(s.Ctx, &dextypes.MsgWithdrawFilledLimitOrder{ Creator: addr.String(), diff --git a/tests/dex/state_withdraw_test.go b/tests/dex/state_withdraw_test.go index ea9518b30..23c53cd38 100644 --- a/tests/dex/state_withdraw_test.go +++ b/tests/dex/state_withdraw_test.go @@ -2,10 +2,12 @@ package dex_state_test import ( "fmt" - dextypes "github.com/neutron-org/neutron/v4/x/dex/types" - "github.com/stretchr/testify/require" "strconv" "testing" + + "github.com/stretchr/testify/require" + + dextypes "github.com/neutron-org/neutron/v4/x/dex/types" ) type withdrawTestParams struct { @@ -75,7 +77,7 @@ func hydrateAllWithdrawTestCases(paramsList []map[string]string) []withdrawTestP tc.PairID = pairID allTCs = append(allTCs, tc) } - + return allTCs } diff --git a/tests/dex/state_withdrawlimitorder_test.go b/tests/dex/state_withdrawlimitorder_test.go index 6306f4e26..b790ec4f5 100644 --- a/tests/dex/state_withdrawlimitorder_test.go +++ b/tests/dex/state_withdrawlimitorder_test.go @@ -1,15 +1,18 @@ package dex_state_test import ( - "cosmossdk.io/math" "errors" "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" - math_utils "github.com/neutron-org/neutron/v4/utils/math" - dextypes "github.com/neutron-org/neutron/v4/x/dex/types" "strconv" "testing" "time" + + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + math_utils "github.com/neutron-org/neutron/v4/utils/math" + dextypes "github.com/neutron-org/neutron/v4/x/dex/types" ) type withdrawLimitOrderTestParams struct { @@ -103,24 +106,16 @@ func (s *DexStateTestSuite) setupWithdrawLimitOrderTest(params withdrawLimitOrde } tick, err := dextypes.CalcTickIndexFromPrice(DefaultStartPrice) s.NoError(err) - tranches, _ := s.App.DexKeeper.LimitOrderTrancheAll(s.Ctx, &dextypes.QueryAllLimitOrderTrancheRequest{ - PairId: params.PairID.CanonicalString(), - TokenIn: params.PairID.Token0, - Pagination: nil, - }) - fmt.Println(tranches) + req := dextypes.QueryGetLimitOrderTrancheRequest{ PairId: params.PairID.CanonicalString(), TickIndex: -1 * tick, TokenIn: params.PairID.Token0, TrancheKey: res.TrancheKey, } - fmt.Println(req) tranchResp, err := s.App.DexKeeper.LimitOrderTranche(s.Ctx, &req) s.NoError(err) - ut, _ := s.App.DexKeeper.GetLimitOrderTrancheUser(s.Ctx, s.creator.String(), tranchResp.LimitOrderTranche.Key.TrancheKey) - fmt.Println("user tranche1: ", ut) return tranchResp.LimitOrderTranche } @@ -133,7 +128,7 @@ func hydrateAllWithdrawLoTestCases(paramsList []map[string]string) []withdrawLim allTCs = append(allTCs, tc) } - //return allTCs + // return allTCs return removeRedundantWithdrawLOTests(allTCs) } @@ -216,10 +211,10 @@ func TestWithdrawLimitOrder(t *testing.T) { s := new(DexStateTestSuite) s.SetT(t) s.SetupTest() - //totalExpectedToSwap := math.ZeroInt() + // totalExpectedToSwap := math.ZeroInt() for i, tc := range testCases { - //if i != 30 { + // if i != 30 { // continue //} t.Run(strconv.Itoa(i), func(t *testing.T) { @@ -243,7 +238,7 @@ func TestWithdrawLimitOrder(t *testing.T) { */ }) } - //s.SetT(t) + // s.SetT(t) //// check at least one `expectedSwapTakerDenom` > 0 - //s.True(totalExpectedToSwap.GT(math.ZeroInt())) + // s.True(totalExpectedToSwap.GT(math.ZeroInt())) } From 63cbe27d2dbfd062f7ede5d0c669a8bc6172b571 Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Wed, 18 Sep 2024 22:59:09 -0400 Subject: [PATCH 14/21] misc cleanup for state_cancel_limit_order_test --- ...st.go => state_cancel_limit_order_test.go} | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) rename tests/dex/{state_cancel_test.go => state_cancel_limit_order_test.go} (83%) diff --git a/tests/dex/state_cancel_test.go b/tests/dex/state_cancel_limit_order_test.go similarity index 83% rename from tests/dex/state_cancel_test.go rename to tests/dex/state_cancel_limit_order_test.go index 02775f0c8..8ff2fa987 100644 --- a/tests/dex/state_cancel_test.go +++ b/tests/dex/state_cancel_limit_order_test.go @@ -6,8 +6,6 @@ import ( "testing" "time" - "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" math_utils "github.com/neutron-org/neutron/v4/utils/math" @@ -18,11 +16,11 @@ type cancelLimitOrderTestParams struct { // State Conditions SharedParams ExistingTokenAHolders string - Filled int + Filled int64 WithdrawnCreator bool WithdrawnOneOther bool Expired bool - OrderType int32 // JIT, GTT, GTC + OrderType dextypes.LimitOrderType // JIT, GTT, GTC } func (p cancelLimitOrderTestParams) printTestInfo(t *testing.T) { @@ -49,22 +47,22 @@ func hydrateCancelLoTestCase(params map[string]string) cancelLimitOrderTestParam } c := cancelLimitOrderTestParams{ ExistingTokenAHolders: params["ExistingTokenAHolders"], - Filled: parseInt(params["Filled"]), + Filled: int64(parseInt(params["Filled"])), WithdrawnCreator: parseBool(params["WithdrawnCreator"]), WithdrawnOneOther: parseBool(params["WithdrawnOneOther"]), Expired: parseBool(params["Expired"]), - OrderType: dextypes.LimitOrderType_value[params["OrderType"]], + OrderType: dextypes.LimitOrderType(dextypes.LimitOrderType_value[params["OrderType"]]), } c.SharedParams.Tick = selltick return c } -func (s *DexStateTestSuite) setupCancelTest(params cancelLimitOrderTestParams) *dextypes.LimitOrderTranche { +func (s *DexStateTestSuite) setupCancelTest(params cancelLimitOrderTestParams) (tranche *dextypes.LimitOrderTranche) { coinA := sdk.NewCoin(params.PairID.Token0, BaseTokenAmountInt) coinB := sdk.NewCoin(params.PairID.Token1, BaseTokenAmountInt.MulRaw(10)) s.FundAcc(s.creator, sdk.NewCoins(coinA)) var expTime *time.Time - if params.OrderType == int32(dextypes.LimitOrderType_GOOD_TIL_TIME) { + if params.OrderType.IsGoodTil() { t := time.Now() expTime = &t } @@ -78,8 +76,8 @@ func (s *DexStateTestSuite) setupCancelTest(params cancelLimitOrderTestParams) * } if params.Filled > 0 { - s.FundAcc(s.bob, sdk.NewCoins(coinB).MulInt(math.NewInt(10))) - fillAmount := totalDeposited.MulRaw(int64(params.Filled)).QuoRaw(100) + s.FundAcc(s.bob, sdk.NewCoins(coinB)) + fillAmount := totalDeposited.MulRaw(params.Filled).QuoRaw(100) _, err := s.makePlaceTakerLO(s.bob, coinB, coinA.Denom, DefaultBuyPriceTaker, dextypes.LimitOrderType_IMMEDIATE_OR_CANCEL, &fillAmount) s.NoError(err) } @@ -95,12 +93,10 @@ func (s *DexStateTestSuite) setupCancelTest(params cancelLimitOrderTestParams) * if params.Expired { s.App.DexKeeper.PurgeExpiredLimitOrders(s.Ctx, time.Now()) } - tick, err := dextypes.CalcTickIndexFromPrice(DefaultStartPrice) - s.NoError(err) req := dextypes.QueryGetLimitOrderTrancheRequest{ PairId: params.PairID.CanonicalString(), - TickIndex: -1 * tick, + TickIndex: -1 * params.Tick, TokenIn: params.PairID.Token0, TrancheKey: res.TrancheKey, } @@ -113,9 +109,11 @@ func (s *DexStateTestSuite) setupCancelTest(params cancelLimitOrderTestParams) * func hydrateAllCancelLoTestCases(paramsList []map[string]string) []cancelLimitOrderTestParams { allTCs := make([]cancelLimitOrderTestParams, 0) for i, paramsRaw := range paramsList { - pairID := generatePairID(i) tc := hydrateCancelLoTestCase(paramsRaw) + + pairID := generatePairID(i) tc.PairID = pairID + allTCs = append(allTCs, tc) } @@ -130,13 +128,13 @@ func removeRedundantCancelLOTests(params []cancelLimitOrderTestParams) []cancelL if p.Filled == 0 && (p.WithdrawnOneOther || p.WithdrawnCreator) { continue } - if p.Expired && p.OrderType == int32(dextypes.LimitOrderType_GOOD_TIL_CANCELLED) { + if p.Expired && p.OrderType.IsGTC() { continue } if p.WithdrawnOneOther && p.ExistingTokenAHolders == CreatorLO { continue } - if p.ExistingTokenAHolders == OneOtherAndCreatorLO && p.OrderType != int32(dextypes.LimitOrderType_GOOD_TIL_CANCELLED) { + if p.ExistingTokenAHolders == OneOtherAndCreatorLO && !p.OrderType.IsGTC() { // user tranches combined into tranches only for LimitOrderType_GOOD_TIL_CANCELLED // it does not make any sense to create two tranches continue @@ -162,8 +160,8 @@ func (s *DexStateTestSuite) assertCalcelAmount(params cancelLimitOrderTestParams // pre-withdrawn (filled/2 or 0) + withdrawn (filled/2 or filled) === filled // converted to TokenB price := dextypes.MustCalcPrice(params.Tick) - expectedBalanceB := price.MulInt(depositSize.MulRaw(int64(params.Filled)).QuoRaw(100)).Ceil().TruncateInt() - expectedBalanceA := depositSize.Sub(depositSize.MulRaw(int64(params.Filled)).QuoRaw(100)) + expectedBalanceB := price.MulInt(depositSize.MulRaw(params.Filled).QuoRaw(100)).Ceil().TruncateInt() + expectedBalanceA := depositSize.Sub(depositSize.MulRaw(params.Filled).QuoRaw(100)) // 1 - withdrawn amount s.assertBalanceWithPrecision(s.creator, params.PairID.Token1, expectedBalanceB, 3) @@ -195,11 +193,11 @@ func TestCancel(t *testing.T) { s.SetT(t) tc.printTestInfo(t) - initialTrancheKey := s.setupCancelTest(tc) + tranche := s.setupCancelTest(tc) - _, err := s.makeCancel(s.creator, initialTrancheKey.Key.TrancheKey) + _, err := s.makeCancel(s.creator, tranche.Key.TrancheKey) s.handleCancelErrors(tc, err) - _, found := s.App.DexKeeper.GetLimitOrderTrancheUser(s.Ctx, s.creator.String(), initialTrancheKey.Key.TrancheKey) + _, found := s.App.DexKeeper.GetLimitOrderTrancheUser(s.Ctx, s.creator.String(), tranche.Key.TrancheKey) s.False(found) s.assertCalcelAmount(tc) }) From 9ff40115c2a84038487e7c7cec36f9377c8bc79e Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Thu, 19 Sep 2024 01:22:47 -0400 Subject: [PATCH 15/21] more misc fixes --- tests/dex/state_cancel_limit_order_test.go | 2 +- tests/dex/state_deposit_test.go | 24 +++--- .../dex/state_place_limit_order_maker_test.go | 10 +-- .../dex/state_place_limit_order_taker_test.go | 73 ++++++++++--------- tests/dex/state_setup_test.go | 13 +--- 5 files changed, 57 insertions(+), 65 deletions(-) diff --git a/tests/dex/state_cancel_limit_order_test.go b/tests/dex/state_cancel_limit_order_test.go index 8ff2fa987..d6d46cc8e 100644 --- a/tests/dex/state_cancel_limit_order_test.go +++ b/tests/dex/state_cancel_limit_order_test.go @@ -36,7 +36,7 @@ func (p cancelLimitOrderTestParams) printTestInfo(t *testing.T) { p.WithdrawnCreator, p.WithdrawnOneOther, p.Expired, - p.OrderType, + p.OrderType.String(), ) } diff --git a/tests/dex/state_deposit_test.go b/tests/dex/state_deposit_test.go index a7670d621..6268367e6 100644 --- a/tests/dex/state_deposit_test.go +++ b/tests/dex/state_deposit_test.go @@ -64,16 +64,16 @@ func (s *DexStateTestSuite) setupDepositState(params DepositState) { s.makeDepositSuccess(s.alice, liquidityDistr, false) case OneOtherAndCreator: - liqDistrArr := splitLiquidityDistribution(liquidityDistr, 2) + splitLiqDistrArr := splitLiquidityDistribution(liquidityDistr, 2) - coins := sdk.NewCoins(liqDistrArr[0].TokenA, liqDistrArr[0].TokenB) + coins := sdk.NewCoins(splitLiqDistrArr.TokenA, splitLiqDistrArr.TokenB) s.FundAcc(s.creator, coins) - coins = sdk.NewCoins(liqDistrArr[1].TokenA, liqDistrArr[1].TokenB) + coins = sdk.NewCoins(splitLiqDistrArr.TokenA, splitLiqDistrArr.TokenB) s.FundAcc(s.alice, coins) - s.makeDepositSuccess(s.creator, liqDistrArr[0], false) - s.makeDepositSuccess(s.alice, liqDistrArr[1], false) + s.makeDepositSuccess(s.creator, splitLiqDistrArr, false) + s.makeDepositSuccess(s.alice, splitLiqDistrArr, false) } // handle pool value increase @@ -150,12 +150,6 @@ func calcCurrentShareValue(params depositTestParams) math_utils.PrecDec { return currentShareValue } -func calcDepositValue(params depositTestParams, depositAmount0, depositAmount1 math.Int) math_utils.PrecDec { - rawValueDeposit := calcDepositValueAsToken0(params.Tick, depositAmount0, depositAmount1) - - return rawValueDeposit -} - func calcAutoSwapResidualValue(params depositTestParams, residual0, residual1 math.Int) math_utils.PrecDec { swapFeeDeduction := dextypes.MustCalcPrice(int64(params.Fee)) @@ -178,7 +172,7 @@ func calcAutoSwapResidualValue(params depositTestParams, residual0, residual1 ma func calcExpectedDepositAmounts(params depositTestParams) (tokenAAmount, tokenBAmount, sharesIssued math.Int) { amountAWithoutAutoswap, amountBWithoutAutoswap := CalcDepositOutput(params) - sharesIssuedWithoutAutoswap := calcDepositValue(params, amountAWithoutAutoswap, amountBWithoutAutoswap) + sharesIssuedWithoutAutoswap := calcDepositValueAsToken0(params.Tick, amountAWithoutAutoswap, amountBWithoutAutoswap) residualA := params.DepositAmounts.TokenA.Amount.Sub(amountAWithoutAutoswap) residualB := params.DepositAmounts.TokenB.Amount.Sub(amountBWithoutAutoswap) @@ -225,6 +219,7 @@ func hydrateDepositTestCase(params map[string]string, pairID *dextypes.PairID) d var valueIncrease LiquidityDistribution if liquidityDistribution.empty() { + // Cannot increase value on empty pool valueIncrease = parseLiquidityDistribution(TokenA0TokenB0, pairID) } else { valueIncrease = parseLiquidityDistribution(params["PoolValueIncrease"], pairID) @@ -270,7 +265,6 @@ func TestDeposit(t *testing.T) { }}, {field: "DisableAutoswap", states: []string{True, False}}, {field: "PoolValueIncrease", states: []string{TokenA0TokenB0, TokenA1TokenB0, TokenA0TokenB1}}, - // {field: "FailTxOnBEL", states: []string{True, False}}, // I don't think this needs to be tested {field: "DepositAmounts", states: []string{ TokenA0TokenB1, TokenA0TokenB2, @@ -297,7 +291,7 @@ func TestDeposit(t *testing.T) { poolID, found := s.App.DexKeeper.GetPoolIDByParams(s.Ctx, tc.PairID, tc.Tick, tc.Fee) if tc.ExistingShareHolders == None { - // This is the ID that will be used when the pool is created + // There is no pool yet. This is the ID that will be used when the pool is created poolID = s.App.DexKeeper.GetPoolCount(s.Ctx) } else { require.True(t, found, "Pool not found after deposit") @@ -308,7 +302,7 @@ func TestDeposit(t *testing.T) { // Do the actual deposit resp, err := s.makeDepositDefault(s.creator, tc.DepositAmounts, tc.DisableAutoswap) - // Assert new state is correct + // Assert that if there is an error it is expected s.handleBaseFailureCases(tc, err) expectedDepositA, expectedDepositB, expectedShares := calcExpectedDepositAmounts(tc) diff --git a/tests/dex/state_place_limit_order_maker_test.go b/tests/dex/state_place_limit_order_maker_test.go index 34afd7e4b..d5561ebf6 100644 --- a/tests/dex/state_place_limit_order_maker_test.go +++ b/tests/dex/state_place_limit_order_maker_test.go @@ -43,7 +43,7 @@ type placeLimitOrderMakerTestParams struct { BehindEnemyLine string PreexistingTraded bool // Message Variants - OrderType int32 // JIT, GTT, GTC + OrderType dextypes.LimitOrderType // JIT, GTT, GTC } func (p placeLimitOrderMakerTestParams) printTestInfo(t *testing.T) { @@ -55,7 +55,7 @@ func (p placeLimitOrderMakerTestParams) printTestInfo(t *testing.T) { p.ExistingTokenAHolders, p.BehindEnemyLine, p.PreexistingTraded, - dextypes.LimitOrderType_name[p.OrderType], + p.OrderType.String(), ) } @@ -100,7 +100,7 @@ func hydratePlaceLOMakerTestCase(params map[string]string, pairID *dextypes.Pair ExistingTokenAHolders: params["ExistingTokenAHolders"], BehindEnemyLine: params["BehindEnemyLines"], PreexistingTraded: parseBool(params["PreexistingTraded"]), - OrderType: dextypes.LimitOrderType_value[params["OrderType"]], + OrderType: dextypes.LimitOrderType(dextypes.LimitOrderType_value[params["OrderType"]]), } } @@ -244,7 +244,7 @@ func (s *DexStateTestSuite) expectedInOutTokensAmount(tokenA sdk.Coin, denomOut func (s *DexStateTestSuite) assertExpectedTrancheKey(initialKey, msgKey string, params placeLimitOrderMakerTestParams) { // we expect initialKey != msgKey - if params.ExistingTokenAHolders == NoneLO || params.PreexistingTraded || params.OrderType == int32(dextypes.LimitOrderType_GOOD_TIL_TIME) || params.OrderType == int32(dextypes.LimitOrderType_JUST_IN_TIME) { + if params.ExistingTokenAHolders == NoneLO || params.PreexistingTraded || params.OrderType.IsGoodTil() || params.OrderType.IsJIT() { s.NotEqual(initialKey, msgKey) return } @@ -283,7 +283,7 @@ func TestPlaceLimitOrderMaker(t *testing.T) { amountIn := sdk.NewCoin(tc.PairID.Token0, math.NewInt(MakerAmountIn)) var expTime *time.Time - if tc.OrderType == int32(dextypes.LimitOrderType_GOOD_TIL_TIME) { + if tc.OrderType.IsGoodTil() { // any time is valid for tests t := time.Now() expTime = &t diff --git a/tests/dex/state_place_limit_order_taker_test.go b/tests/dex/state_place_limit_order_taker_test.go index fee669c47..d5484d664 100644 --- a/tests/dex/state_place_limit_order_taker_test.go +++ b/tests/dex/state_place_limit_order_taker_test.go @@ -12,6 +12,7 @@ import ( math_utils "github.com/neutron-org/neutron/v4/utils/math" dextypes "github.com/neutron-org/neutron/v4/x/dex/types" + "github.com/neutron-org/neutron/v4/x/dex/utils" ) // LiquidityType @@ -41,7 +42,7 @@ type placeLimitOrderTakerTestParams struct { TicksDistribution []int64 // Message Variants - OrderType int32 // FillOrKill or ImmediateOrCancel + OrderType dextypes.LimitOrderType // FillOrKill or ImmediateOrCancel AmountIn sdk.Coin LimitPrice math_utils.PrecDec MaxAmountOut *math.Int @@ -49,7 +50,7 @@ type placeLimitOrderTakerTestParams struct { func (p placeLimitOrderTakerTestParams) printTestInfo(t *testing.T) { t.Logf(` - LiquidityType: %s + LiquidityType: %s TicksDistribution: %v OrderType: %v AmountIn: %v @@ -57,7 +58,7 @@ func (p placeLimitOrderTakerTestParams) printTestInfo(t *testing.T) { MaxAmountOut: %v`, p.LiquidityType, p.TicksDistribution, - dextypes.LimitOrderType_name[p.OrderType], + p.OrderType.String(), p.AmountIn, p.LimitPrice, p.MaxAmountOut, @@ -103,7 +104,7 @@ func hydratePlaceLOTakerTestCase(params map[string]string, pairID *dextypes.Pair return placeLimitOrderTakerTestParams{ LiquidityType: params["LiquidityType"], TicksDistribution: generateTicks(ticks), - OrderType: dextypes.LimitOrderType_value[params["OrderType"]], + OrderType: dextypes.LimitOrderType(dextypes.LimitOrderType_value[params["OrderType"]]), AmountIn: sdk.NewCoin(pairID.Token1, amountIn), MaxAmountOut: maxAmountOut, LimitPrice: math_utils.OnePrecDec().Quo(LimitPrice.Add(DefaultPriceDelta)), @@ -164,23 +165,6 @@ func (s *DexStateTestSuite) setupLoTakerState(params placeLimitOrderTakerTestPar } } -func MaxAmountAOut(params placeLimitOrderTakerTestParams) math.Int { - // liquidity equally distributed over the ticks with a delta `DefaultPriceDelta` starting from `DefaultStartPrice` - // we find amount of ticks (tickOffset) are being covered by limitPrice - // and that is the max liquidity we can swap in - - if params.LiquidityType == None { - return math.ZeroInt() - } - // see `setupLoTakerState` - tickLiquidity := BaseTokenAmountInt.QuoRaw(int64(len(params.TicksDistribution))) - - tickOffset := math_utils.OnePrecDec().Quo(params.LimitPrice).Sub(DefaultStartPrice).Quo(DefaultPriceDelta).Ceil().TruncateInt() - liquidity := tickLiquidity.Mul(tickOffset) - - return math.MinInt(liquidity, BaseTokenAmountInt) -} - func ExpectedInOut(params placeLimitOrderTakerTestParams) (math.Int, math.Int) { if params.LiquidityType == None { return math.ZeroInt(), math.ZeroInt() @@ -196,26 +180,45 @@ func ExpectedInOut(params placeLimitOrderTakerTestParams) (math.Int, math.Int) { if LimitTick > tick { break } - toOut := tickLiquidity + price := dextypes.MustCalcPrice(-1 * tick) + remainingIn := params.AmountIn.Amount.Sub(TotalIn) + + if !remainingIn.IsPositive() { + break + } + + availableLiquidity := tickLiquidity + outGivenIn := price.MulInt(remainingIn).TruncateInt() + amountOut := math.ZeroInt() if params.MaxAmountOut != nil { - toOut = math.MinInt(toOut, params.MaxAmountOut.Sub(TotalOut)) + maxAmountOut := params.MaxAmountOut.Sub(TotalOut) + if !maxAmountOut.IsPositive() { + break + } + amountOut = utils.MinIntArr([]math.Int{availableLiquidity, outGivenIn, maxAmountOut}) + } else { + amountOut = utils.MinIntArr([]math.Int{availableLiquidity, outGivenIn}) + } - toIn := dextypes.MustCalcPrice(tick).MulInt(toOut).Ceil().TruncateInt() - if toIn.GT(params.AmountIn.Amount.Sub(TotalIn)) { - toIn = params.AmountIn.Amount.Sub(TotalIn) - toOut = dextypes.MustCalcPrice(-1 * tick).MulInt(toIn).Ceil().TruncateInt() + amountInRaw := math_utils.NewPrecDecFromInt(amountOut).Quo(price) + + amountIn := math.ZeroInt() + if params.LiquidityType == LOLP { + // Simulate rounding on two different tickLiquidities + amountIn = amountInRaw.QuoInt(math.NewInt(2)).Ceil().TruncateInt().MulRaw(2) + } else { + amountIn = amountInRaw.Ceil().TruncateInt() } - TotalIn = TotalIn.Add( - toIn, - ) - TotalOut = TotalOut.Add(toOut) + + TotalIn = TotalIn.Add(amountIn) + TotalOut = TotalOut.Add(amountOut) } return TotalIn, TotalOut } func (s *DexStateTestSuite) handleTakerErrors(params placeLimitOrderTakerTestParams, err error) { - if params.OrderType == int32(dextypes.LimitOrderType_FILL_OR_KILL) { + if params.OrderType.IsFoK() { maxIn, _ := ExpectedInOut(params) if maxIn.LT(params.AmountIn.Amount) { if errors.Is(err, dextypes.ErrFoKLimitOrderNotFilled) { @@ -262,8 +265,8 @@ func TestPlaceLimitOrderTaker(t *testing.T) { expIn, expOut := ExpectedInOut(tc) // TODO: fix rounding issues - s.intsApproxEqual("", expIn, resp.CoinIn.Amount, 10) - s.intsApproxEqual("", expOut, resp.TakerCoinOut.Amount, 10) + s.intsApproxEqual("swapAmountIn", expIn, resp.CoinIn.Amount, 1) + s.intsApproxEqual("swapAmountOut", expOut, resp.TakerCoinOut.Amount, 0) s.True( tc.LimitPrice.MulInt(resp.CoinIn.Amount).TruncateInt().LTE(resp.TakerCoinOut.Amount), @@ -273,7 +276,7 @@ func TestPlaceLimitOrderTaker(t *testing.T) { s.True(resp.TakerCoinOut.Amount.LTE(*tc.MaxAmountOut)) } - if tc.OrderType == int32(dextypes.LimitOrderType_FILL_OR_KILL) { + if tc.OrderType.IsFoK() { // we should fill either AmountIn or MaxAmountOut s.Condition(func() bool { if tc.MaxAmountOut != nil { diff --git a/tests/dex/state_setup_test.go b/tests/dex/state_setup_test.go index 875daf0ef..25a904e7d 100644 --- a/tests/dex/state_setup_test.go +++ b/tests/dex/state_setup_test.go @@ -134,20 +134,15 @@ func (l LiquidityDistribution) hasTokenB() bool { return l.TokenB.Amount.IsPositive() } -func splitLiquidityDistribution(liquidityDistribution LiquidityDistribution, n int64) []LiquidityDistribution { +func splitLiquidityDistribution(liquidityDistribution LiquidityDistribution, n int64) LiquidityDistribution { nInt := math.NewInt(n) amount0 := liquidityDistribution.TokenA.Amount.Quo(nInt) amount1 := liquidityDistribution.TokenB.Amount.Quo(nInt) - result := make([]LiquidityDistribution, n) - for i := range n { - result[i] = LiquidityDistribution{ - TokenA: sdk.NewCoin(liquidityDistribution.TokenA.Denom, amount0), - TokenB: sdk.NewCoin(liquidityDistribution.TokenB.Denom, amount1), - } + return LiquidityDistribution{ + TokenA: sdk.NewCoin(liquidityDistribution.TokenA.Denom, amount0), + TokenB: sdk.NewCoin(liquidityDistribution.TokenB.Denom, amount1), } - - return result } // State Parsers ////////////////////////////////////////////////////////////// From 20100a6efc2f2f6d5b5a15519348f4b414500c28 Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Thu, 19 Sep 2024 01:24:44 -0400 Subject: [PATCH 16/21] rename --- ...hdrawlimitorder_test.go => state_withdraw_limit_order_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/dex/{state_withdrawlimitorder_test.go => state_withdraw_limit_order_test.go} (100%) diff --git a/tests/dex/state_withdrawlimitorder_test.go b/tests/dex/state_withdraw_limit_order_test.go similarity index 100% rename from tests/dex/state_withdrawlimitorder_test.go rename to tests/dex/state_withdraw_limit_order_test.go From 6651e4cb00db55e85040f8daff2aec7adb90d717 Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Tue, 12 Nov 2024 16:23:10 -0500 Subject: [PATCH 17/21] updates to v5 --- tests/dex/state_cancel_limit_order_test.go | 10 +++++----- tests/dex/state_deposit_test.go | 6 +++--- tests/dex/state_place_limit_order_maker_test.go | 4 ++-- tests/dex/state_place_limit_order_taker_test.go | 6 +++--- tests/dex/state_setup_test.go | 10 +++++----- tests/dex/state_withdraw_limit_order_test.go | 4 ++-- tests/dex/state_withdraw_test.go | 2 +- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/dex/state_cancel_limit_order_test.go b/tests/dex/state_cancel_limit_order_test.go index d6d46cc8e..fab1ac7c1 100644 --- a/tests/dex/state_cancel_limit_order_test.go +++ b/tests/dex/state_cancel_limit_order_test.go @@ -8,8 +8,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - math_utils "github.com/neutron-org/neutron/v4/utils/math" - dextypes "github.com/neutron-org/neutron/v4/x/dex/types" + math_utils "github.com/neutron-org/neutron/v5/utils/math" + dextypes "github.com/neutron-org/neutron/v5/x/dex/types" ) type cancelLimitOrderTestParams struct { @@ -96,7 +96,7 @@ func (s *DexStateTestSuite) setupCancelTest(params cancelLimitOrderTestParams) ( req := dextypes.QueryGetLimitOrderTrancheRequest{ PairId: params.PairID.CanonicalString(), - TickIndex: -1 * params.Tick, + TickIndex: params.Tick, TokenIn: params.PairID.Token0, TrancheKey: res.TrancheKey, } @@ -145,8 +145,8 @@ func removeRedundantCancelLOTests(params []cancelLimitOrderTestParams) []cancelL } func (s *DexStateTestSuite) handleCancelErrors(params cancelLimitOrderTestParams, err error) { - if params.Expired || params.Filled == 100 { - if errors.Is(dextypes.ErrActiveLimitOrderNotFound, err) { + if params.Filled == 100 && params.WithdrawnCreator { + if errors.Is(dextypes.ErrValidLimitOrderTrancheNotFound, err) { s.T().Skip() } } diff --git a/tests/dex/state_deposit_test.go b/tests/dex/state_deposit_test.go index 6268367e6..f18ca48d4 100644 --- a/tests/dex/state_deposit_test.go +++ b/tests/dex/state_deposit_test.go @@ -8,8 +8,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" - math_utils "github.com/neutron-org/neutron/v4/utils/math" - dextypes "github.com/neutron-org/neutron/v4/x/dex/types" + math_utils "github.com/neutron-org/neutron/v5/utils/math" + dextypes "github.com/neutron-org/neutron/v5/x/dex/types" ) type DepositState struct { @@ -85,7 +85,7 @@ func (s *DexStateTestSuite) setupDepositState(params DepositState) { pool.LowerTick0.ReservesMakerDenom = pool.LowerTick0.ReservesMakerDenom.Add(params.PoolValueIncrease.TokenA.Amount) pool.UpperTick1.ReservesMakerDenom = pool.UpperTick1.ReservesMakerDenom.Add(params.PoolValueIncrease.TokenB.Amount) - s.App.DexKeeper.SetPool(s.Ctx, pool) + s.App.DexKeeper.UpdatePool(s.Ctx, pool) // Add fund dex with the additional balance err := s.App.BankKeeper.MintCoins(s.Ctx, dextypes.ModuleName, sdk.NewCoins(params.PoolValueIncrease.TokenA, params.PoolValueIncrease.TokenB)) diff --git a/tests/dex/state_place_limit_order_maker_test.go b/tests/dex/state_place_limit_order_maker_test.go index d5561ebf6..820b40ae3 100644 --- a/tests/dex/state_place_limit_order_maker_test.go +++ b/tests/dex/state_place_limit_order_maker_test.go @@ -8,8 +8,8 @@ import ( "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - math_utils "github.com/neutron-org/neutron/v4/utils/math" - dextypes "github.com/neutron-org/neutron/v4/x/dex/types" + math_utils "github.com/neutron-org/neutron/v5/utils/math" + dextypes "github.com/neutron-org/neutron/v5/x/dex/types" ) // ExistingTokenAHolders diff --git a/tests/dex/state_place_limit_order_taker_test.go b/tests/dex/state_place_limit_order_taker_test.go index d5484d664..bf0b47eb1 100644 --- a/tests/dex/state_place_limit_order_taker_test.go +++ b/tests/dex/state_place_limit_order_taker_test.go @@ -10,9 +10,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - math_utils "github.com/neutron-org/neutron/v4/utils/math" - dextypes "github.com/neutron-org/neutron/v4/x/dex/types" - "github.com/neutron-org/neutron/v4/x/dex/utils" + math_utils "github.com/neutron-org/neutron/v5/utils/math" + dextypes "github.com/neutron-org/neutron/v5/x/dex/types" + "github.com/neutron-org/neutron/v5/x/dex/utils" ) // LiquidityType diff --git a/tests/dex/state_setup_test.go b/tests/dex/state_setup_test.go index 25a904e7d..9980f91a9 100644 --- a/tests/dex/state_setup_test.go +++ b/tests/dex/state_setup_test.go @@ -11,11 +11,11 @@ import ( "github.com/cosmos/cosmos-sdk/types/query" "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/neutron-org/neutron/v4/testutil/apptesting" - "github.com/neutron-org/neutron/v4/testutil/common/sample" - math_utils "github.com/neutron-org/neutron/v4/utils/math" - dexkeeper "github.com/neutron-org/neutron/v4/x/dex/keeper" - dextypes "github.com/neutron-org/neutron/v4/x/dex/types" + dextypes "github.com/neutron-org/neutron/v5/x/dex/types" + "github.com/neutron-org/neutron/v5/testutil/apptesting" + "github.com/neutron-org/neutron/v5/testutil/common/sample" + math_utils "github.com/neutron-org/neutron/v5/utils/math" + dexkeeper "github.com/neutron-org/neutron/v5/x/dex/keeper" ) // Constants ////////////////////////////////////////////////////////////////// diff --git a/tests/dex/state_withdraw_limit_order_test.go b/tests/dex/state_withdraw_limit_order_test.go index b790ec4f5..f6eabdd2f 100644 --- a/tests/dex/state_withdraw_limit_order_test.go +++ b/tests/dex/state_withdraw_limit_order_test.go @@ -11,8 +11,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - math_utils "github.com/neutron-org/neutron/v4/utils/math" - dextypes "github.com/neutron-org/neutron/v4/x/dex/types" + math_utils "github.com/neutron-org/neutron/v5/utils/math" + dextypes "github.com/neutron-org/neutron/v5/x/dex/types" ) type withdrawLimitOrderTestParams struct { diff --git a/tests/dex/state_withdraw_test.go b/tests/dex/state_withdraw_test.go index 23c53cd38..32f8fe663 100644 --- a/tests/dex/state_withdraw_test.go +++ b/tests/dex/state_withdraw_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" - dextypes "github.com/neutron-org/neutron/v4/x/dex/types" + dextypes "github.com/neutron-org/neutron/v5/x/dex/types" ) type withdrawTestParams struct { From ad9f144f51e0770cdffeab8b54bb4cfbba02deb2 Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Thu, 14 Nov 2024 14:38:18 -0500 Subject: [PATCH 18/21] fix deposit tests --- tests/dex/state_deposit_test.go | 104 ++++++++++++++++++++------------ tests/dex/state_setup_test.go | 4 +- 2 files changed, 68 insertions(+), 40 deletions(-) diff --git a/tests/dex/state_deposit_test.go b/tests/dex/state_deposit_test.go index f18ca48d4..30bef967a 100644 --- a/tests/dex/state_deposit_test.go +++ b/tests/dex/state_deposit_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" math_utils "github.com/neutron-org/neutron/v5/utils/math" + "github.com/neutron-org/neutron/v5/x/dex/types" dextypes "github.com/neutron-org/neutron/v5/x/dex/types" ) @@ -100,7 +101,7 @@ func CalcTotalPreDepositLiquidity(params depositTestParams) LiquidityDistributio } } -func CalcDepositOutput(params depositTestParams) (resultAmountA, resultAmountB math.Int) { +func CalcDepositAmountNoAutoswap(params depositTestParams) (resultAmountA, resultAmountB math.Int) { depositA := params.DepositAmounts.TokenA.Amount depositB := params.DepositAmounts.TokenB.Amount @@ -131,7 +132,8 @@ func CalcDepositOutput(params depositTestParams) (resultAmountA, resultAmountB m } } -func calcCurrentShareValue(params depositTestParams) math_utils.PrecDec { +func calcCurrentShareValue(params depositTestParams, existingValue math_utils.PrecDec) math_utils.PrecDec { + initialValueA := params.ExistingLiquidityDistribution.TokenA.Amount initialValueB := params.ExistingLiquidityDistribution.TokenB.Amount @@ -140,64 +142,90 @@ func calcCurrentShareValue(params depositTestParams) math_utils.PrecDec { return math_utils.OnePrecDec() } - totalValueA := initialValueA.Add(params.PoolValueIncrease.TokenA.Amount) - totalValueB := initialValueB.Add(params.PoolValueIncrease.TokenB.Amount) - - totalPreDepositValue := calcDepositValueAsToken0(params.Tick, totalValueA, totalValueB) - - currentShareValue := math_utils.NewPrecDecFromInt(existingShares).Quo(totalPreDepositValue) + currentShareValue := math_utils.NewPrecDecFromInt(existingShares).Quo(existingValue) return currentShareValue } -func calcAutoSwapResidualValue(params depositTestParams, residual0, residual1 math.Int) math_utils.PrecDec { - swapFeeDeduction := dextypes.MustCalcPrice(int64(params.Fee)) +func calcAutoswapAmount(params depositTestParams) (swapAmountA math.Int, swapAmountB math.Int) { + existingLiquidity := CalcTotalPreDepositLiquidity(params) + existingA := existingLiquidity.TokenA.Amount + existingB := existingLiquidity.TokenB.Amount + depositAmountA := params.DepositAmounts.TokenA.Amount + depositAmountB := params.DepositAmounts.TokenB.Amount + price1To0 := types.MustCalcPrice(-params.Tick) + if existingA.IsZero() && existingB.IsZero() { + return math.ZeroInt(), math.ZeroInt() + } + + existingADec := math_utils.NewPrecDecFromInt(existingA) + existingBDec := math_utils.NewPrecDecFromInt(existingB) + // swapAmount = (reserves0*depositAmount1 - reserves1*depositAmount0) / (price * reserves1 + reserves0) + swapAmount := existingADec.MulInt(depositAmountB).Sub(existingBDec.MulInt(depositAmountA)). + Quo(existingADec.Add(existingBDec.Quo(price1To0))) switch { - // We must autoswap TokenA - case residual0.IsPositive() && residual1.IsPositive(): - panic("residual0 and residual1 cannot both be positive") - case residual0.IsPositive(): - return swapFeeDeduction.MulInt(residual0) - case residual1.IsPositive(): - price1To0CenterTick := dextypes.MustCalcPrice(-1 * params.Tick) - token1AsToken0 := price1To0CenterTick.MulInt(residual1) - return swapFeeDeduction.Mul(token1AsToken0) - default: - panic("residual0 and residual1 cannot both be zero") + case swapAmount.IsZero(): // nothing to be swapped + return math.ZeroInt(), math.ZeroInt() + case swapAmount.IsPositive(): // Token1 needs to be swapped + return math.ZeroInt(), swapAmount.Ceil().TruncateInt() + + default: // Token0 needs to be swapped + amountSwappedAs1 := swapAmount.Neg() + + amountSwapped0 := amountSwappedAs1.Quo(price1To0) + return amountSwapped0.Ceil().TruncateInt(), math.ZeroInt() } + } func calcExpectedDepositAmounts(params depositTestParams) (tokenAAmount, tokenBAmount, sharesIssued math.Int) { - amountAWithoutAutoswap, amountBWithoutAutoswap := CalcDepositOutput(params) + var depositValueAsToken0 math_utils.PrecDec + var inAmountA math.Int + var inAmountB math.Int - sharesIssuedWithoutAutoswap := calcDepositValueAsToken0(params.Tick, amountAWithoutAutoswap, amountBWithoutAutoswap) + existingLiquidity := CalcTotalPreDepositLiquidity(params) + existingA := existingLiquidity.TokenA.Amount + existingB := existingLiquidity.TokenB.Amount + existingValueAsToken0 := calcDepositValueAsToken0(params.Tick, existingA, existingB) - residualA := params.DepositAmounts.TokenA.Amount.Sub(amountAWithoutAutoswap) - residualB := params.DepositAmounts.TokenB.Amount.Sub(amountBWithoutAutoswap) + if params.DisableAutoswap { + inAmountA, inAmountB = CalcDepositAmountNoAutoswap(params) + depositValueAsToken0 = calcDepositValueAsToken0(params.Tick, inAmountA, inAmountB) - autoswapSharesIssued := math_utils.ZeroPrecDec() - if !params.DisableAutoswap && (residualA.IsPositive() || residualB.IsPositive()) { - autoswapSharesIssued = calcAutoSwapResidualValue(params, residualA, residualB) - tokenAAmount = params.DepositAmounts.TokenA.Amount - tokenBAmount = params.DepositAmounts.TokenB.Amount + shareValue := calcCurrentShareValue(params, existingValueAsToken0) + sharesIssued = depositValueAsToken0.Mul(shareValue).TruncateInt() + + return inAmountA, inAmountB, sharesIssued } else { - tokenAAmount = amountAWithoutAutoswap - tokenBAmount = amountBWithoutAutoswap - } + autoSwapAmountA, autoswapAmountB := calcAutoswapAmount(params) + autoswapValueAsToken0 := calcDepositValueAsToken0(params.Tick, autoSwapAmountA, autoswapAmountB) + + autoswapFeeAsPrice := types.MustCalcPrice(-int64(params.Fee)) + autoswapFeePct := math_utils.OnePrecDec().Sub(autoswapFeeAsPrice) + autoswapFee := autoswapValueAsToken0.Mul(autoswapFeePct) - totalDepositValue := autoswapSharesIssued.Add(sharesIssuedWithoutAutoswap) - currentShareValue := calcCurrentShareValue(params) - sharesIssued = totalDepositValue.Mul(currentShareValue).TruncateInt() + inAmountA := params.DepositAmounts.TokenA.Amount + inAmountB := params.DepositAmounts.TokenB.Amount + + fullDepositValueAsToken0 := calcDepositValueAsToken0(params.Tick, inAmountA, inAmountB) + depositAmountMinusFee := fullDepositValueAsToken0.Sub(autoswapFee) + currentValueWithAutoswapFee := existingValueAsToken0.Add(autoswapFee) + shareValue := calcCurrentShareValue(params, currentValueWithAutoswapFee) + + sharesIssued = depositAmountMinusFee.Mul(shareValue).TruncateInt() + + return inAmountA, inAmountB, sharesIssued + + } - return tokenAAmount, tokenBAmount, sharesIssued } func (s *DexStateTestSuite) handleBaseFailureCases(params depositTestParams, err error) { currentLiquidity := CalcTotalPreDepositLiquidity(params) // cannot deposit single sided liquidity into a non-empty pool if you are missing one of the tokens in the pool - if !currentLiquidity.empty() { + if !currentLiquidity.empty() && params.DisableAutoswap { if (!params.DepositAmounts.hasTokenA() && currentLiquidity.hasTokenA()) || (!params.DepositAmounts.hasTokenB() && currentLiquidity.hasTokenB()) { s.ErrorIs(err, dextypes.ErrZeroTrueDeposit) s.T().Skip("Ending test due to expected error") diff --git a/tests/dex/state_setup_test.go b/tests/dex/state_setup_test.go index 9980f91a9..ca878601b 100644 --- a/tests/dex/state_setup_test.go +++ b/tests/dex/state_setup_test.go @@ -11,11 +11,11 @@ import ( "github.com/cosmos/cosmos-sdk/types/query" "github.com/cosmos/cosmos-sdk/x/bank/types" - dextypes "github.com/neutron-org/neutron/v5/x/dex/types" "github.com/neutron-org/neutron/v5/testutil/apptesting" "github.com/neutron-org/neutron/v5/testutil/common/sample" math_utils "github.com/neutron-org/neutron/v5/utils/math" dexkeeper "github.com/neutron-org/neutron/v5/x/dex/keeper" + dextypes "github.com/neutron-org/neutron/v5/x/dex/types" ) // Constants ////////////////////////////////////////////////////////////////// @@ -334,7 +334,7 @@ func (s *DexStateTestSuite) makeWithdrawFilledSuccess(addr sdk.AccAddress, tranc } func calcDepositValueAsToken0(tick int64, amount0, amount1 math.Int) math_utils.PrecDec { - price1To0CenterTick := dextypes.MustCalcPrice(-1 * tick) + price1To0CenterTick := dextypes.MustCalcPrice(tick) amount1ValueAsToken0 := price1To0CenterTick.MulInt(amount1) depositValue := amount1ValueAsToken0.Add(math_utils.NewPrecDecFromInt(amount0)) From 69b7645f77da020ae6e949e2380b680c52cd836f Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Thu, 14 Nov 2024 15:12:23 -0500 Subject: [PATCH 19/21] fix taker lo tests --- tests/dex/state_place_limit_order_taker_test.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/dex/state_place_limit_order_taker_test.go b/tests/dex/state_place_limit_order_taker_test.go index bf0b47eb1..a04b217c8 100644 --- a/tests/dex/state_place_limit_order_taker_test.go +++ b/tests/dex/state_place_limit_order_taker_test.go @@ -159,7 +159,7 @@ func (s *DexStateTestSuite) setupLoTakerState(params placeLimitOrderTakerTestPar TokenB: sdk.NewCoin(params.PairID.Token1, math.ZeroInt()), } // tick+DefaultFee to put liquidity the same tick as LO - _, err := s.makeDeposit(s.alice, liduidity, DefaultFee, tick+DefaultFee, true) + _, err := s.makeDeposit(s.alice, liduidity, DefaultFee, -tick+DefaultFee, true) s.NoError(err) } } @@ -169,18 +169,22 @@ func ExpectedInOut(params placeLimitOrderTakerTestParams) (math.Int, math.Int) { if params.LiquidityType == None { return math.ZeroInt(), math.ZeroInt() } - LimitTick, err := dextypes.CalcTickIndexFromPrice(math_utils.OnePrecDec().Quo(params.LimitPrice)) + limitSellTick, err := dextypes.CalcTickIndexFromPrice(params.LimitPrice) if err != nil { panic(err) } + + limitBuyTick := limitSellTick * -1 + tickLiquidity := BaseTokenAmountInt.QuoRaw(int64(len(params.TicksDistribution))) TotalIn := math.ZeroInt() TotalOut := math.ZeroInt() for _, tick := range params.TicksDistribution { - if LimitTick > tick { + + if limitBuyTick < tick { break } - price := dextypes.MustCalcPrice(-1 * tick) + price := dextypes.MustCalcPrice(tick) remainingIn := params.AmountIn.Amount.Sub(TotalIn) if !remainingIn.IsPositive() { @@ -188,7 +192,7 @@ func ExpectedInOut(params placeLimitOrderTakerTestParams) (math.Int, math.Int) { } availableLiquidity := tickLiquidity - outGivenIn := price.MulInt(remainingIn).TruncateInt() + outGivenIn := math_utils.NewPrecDecFromInt(remainingIn).Quo(price).TruncateInt() amountOut := math.ZeroInt() if params.MaxAmountOut != nil { maxAmountOut := params.MaxAmountOut.Sub(TotalOut) @@ -201,7 +205,7 @@ func ExpectedInOut(params placeLimitOrderTakerTestParams) (math.Int, math.Int) { } - amountInRaw := math_utils.NewPrecDecFromInt(amountOut).Quo(price) + amountInRaw := price.MulInt(amountOut) amountIn := math.ZeroInt() if params.LiquidityType == LOLP { From 54913b4fd7a23bd1892c711c2faa5bef23001435 Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Thu, 14 Nov 2024 15:36:07 -0500 Subject: [PATCH 20/21] fix all tests --- tests/dex/state_place_limit_order_maker_test.go | 4 ++-- tests/dex/state_withdraw_limit_order_test.go | 9 ++------- tests/dex/state_withdraw_test.go | 4 ++-- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/tests/dex/state_place_limit_order_maker_test.go b/tests/dex/state_place_limit_order_maker_test.go index 820b40ae3..c6eee2d01 100644 --- a/tests/dex/state_place_limit_order_maker_test.go +++ b/tests/dex/state_place_limit_order_maker_test.go @@ -229,11 +229,11 @@ func (s *DexStateTestSuite) expectedInOutTokensAmount(tokenA sdk.Coin, denomOut // t.ReservesMakerDenom - reserve TokenB we are going to get // t.Price() - price taker -> maker => 1/t.Price() - maker -> taker // maxSwap - max amount of tokenA (ReservesTakerDenom) tranche can consume us by changing ReservesMakerDenom -> ReservesTakerDenom - maxSwap := math_utils.NewPrecDecFromInt(t.ReservesMakerDenom).Quo(t.Price()).TruncateInt() + maxSwap := math_utils.NewPrecDecFromInt(t.ReservesMakerDenom).Mul(t.Price()).TruncateInt() // we can swap full our tranche if maxSwap.GTE(reserveA) { // expected to get tokenB = tokenA* - amountOut = amountOut.Add(math_utils.NewPrecDecFromInt(reserveA).Mul(t.Price()).TruncateInt()) + amountOut = amountOut.Add(math_utils.NewPrecDecFromInt(reserveA).Quo(t.Price()).TruncateInt()) break } reserveA = reserveA.Sub(maxSwap) diff --git a/tests/dex/state_withdraw_limit_order_test.go b/tests/dex/state_withdraw_limit_order_test.go index f6eabdd2f..41b47d2ff 100644 --- a/tests/dex/state_withdraw_limit_order_test.go +++ b/tests/dex/state_withdraw_limit_order_test.go @@ -109,7 +109,7 @@ func (s *DexStateTestSuite) setupWithdrawLimitOrderTest(params withdrawLimitOrde req := dextypes.QueryGetLimitOrderTrancheRequest{ PairId: params.PairID.CanonicalString(), - TickIndex: -1 * tick, + TickIndex: tick, TokenIn: params.PairID.Token0, TrancheKey: res.TrancheKey, } @@ -214,9 +214,6 @@ func TestWithdrawLimitOrder(t *testing.T) { // totalExpectedToSwap := math.ZeroInt() for i, tc := range testCases { - // if i != 30 { - // continue - //} t.Run(strconv.Itoa(i), func(t *testing.T) { s.SetT(t) tc.printTestInfo(t) @@ -238,7 +235,5 @@ func TestWithdrawLimitOrder(t *testing.T) { */ }) } - // s.SetT(t) - //// check at least one `expectedSwapTakerDenom` > 0 - // s.True(totalExpectedToSwap.GT(math.ZeroInt())) + } diff --git a/tests/dex/state_withdraw_test.go b/tests/dex/state_withdraw_test.go index 32f8fe663..a52cab15d 100644 --- a/tests/dex/state_withdraw_test.go +++ b/tests/dex/state_withdraw_test.go @@ -142,11 +142,11 @@ func TestWithdraw(t *testing.T) { TokenBBalanceAfter := balancesAfter.Creator.AmountOf(tc.ExistingLiquidityDistribution.TokenB.Denom) // Assertion 1 // toWithdraw = withdrawnTokenA + withdrawnTokenB*priceTakerToMaker - priceTakerToMaker := dextypes.MustCalcPrice(-1 * tc.Tick) + price1To0 := dextypes.MustCalcPrice(tc.Tick) s.Require().Equal( toWithdraw, TokenABalanceAfter.Sub(TokenABalanceBefore).Add( - priceTakerToMaker.MulInt(TokenBBalanceAfter.Sub(TokenBBalanceBefore)).TruncateInt(), + price1To0.MulInt(TokenBBalanceAfter.Sub(TokenBBalanceBefore)).TruncateInt(), ), ) newExistingSharesOwned := balancesAfter.Creator.AmountOf(poolDenom) From 57bceea588e689558b4685762742c1c1d11db970 Mon Sep 17 00:00:00 2001 From: Julian Compagni Portis Date: Thu, 14 Nov 2024 15:50:28 -0500 Subject: [PATCH 21/21] lint --- tests/dex/state_cancel_limit_order_test.go | 4 +- tests/dex/state_deposit_test.go | 38 ++++++++----------- .../dex/state_place_limit_order_maker_test.go | 2 +- .../dex/state_place_limit_order_taker_test.go | 7 ++-- tests/dex/state_withdraw_limit_order_test.go | 1 - 5 files changed, 22 insertions(+), 30 deletions(-) diff --git a/tests/dex/state_cancel_limit_order_test.go b/tests/dex/state_cancel_limit_order_test.go index fab1ac7c1..9d1821e1c 100644 --- a/tests/dex/state_cancel_limit_order_test.go +++ b/tests/dex/state_cancel_limit_order_test.go @@ -66,13 +66,13 @@ func (s *DexStateTestSuite) setupCancelTest(params cancelLimitOrderTestParams) ( t := time.Now() expTime = &t } - res := s.makePlaceLOSuccess(s.creator, coinA, coinB.Denom, DefaultSellPrice, dextypes.LimitOrderType(params.OrderType), expTime) + res := s.makePlaceLOSuccess(s.creator, coinA, coinB.Denom, DefaultSellPrice, params.OrderType, expTime) totalDeposited := BaseTokenAmountInt if params.ExistingTokenAHolders == OneOtherAndCreatorLO { totalDeposited = totalDeposited.MulRaw(2) s.FundAcc(s.alice, sdk.NewCoins(coinA)) - s.makePlaceLOSuccess(s.alice, coinA, coinB.Denom, DefaultSellPrice, dextypes.LimitOrderType(params.OrderType), expTime) + s.makePlaceLOSuccess(s.alice, coinA, coinB.Denom, DefaultSellPrice, params.OrderType, expTime) } if params.Filled > 0 { diff --git a/tests/dex/state_deposit_test.go b/tests/dex/state_deposit_test.go index 30bef967a..2eed60742 100644 --- a/tests/dex/state_deposit_test.go +++ b/tests/dex/state_deposit_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/require" math_utils "github.com/neutron-org/neutron/v5/utils/math" - "github.com/neutron-org/neutron/v5/x/dex/types" dextypes "github.com/neutron-org/neutron/v5/x/dex/types" ) @@ -133,7 +132,6 @@ func CalcDepositAmountNoAutoswap(params depositTestParams) (resultAmountA, resul } func calcCurrentShareValue(params depositTestParams, existingValue math_utils.PrecDec) math_utils.PrecDec { - initialValueA := params.ExistingLiquidityDistribution.TokenA.Amount initialValueB := params.ExistingLiquidityDistribution.TokenB.Amount @@ -147,13 +145,13 @@ func calcCurrentShareValue(params depositTestParams, existingValue math_utils.Pr return currentShareValue } -func calcAutoswapAmount(params depositTestParams) (swapAmountA math.Int, swapAmountB math.Int) { +func calcAutoswapAmount(params depositTestParams) (swapAmountA, swapAmountB math.Int) { existingLiquidity := CalcTotalPreDepositLiquidity(params) existingA := existingLiquidity.TokenA.Amount existingB := existingLiquidity.TokenB.Amount depositAmountA := params.DepositAmounts.TokenA.Amount depositAmountB := params.DepositAmounts.TokenB.Amount - price1To0 := types.MustCalcPrice(-params.Tick) + price1To0 := dextypes.MustCalcPrice(-params.Tick) if existingA.IsZero() && existingB.IsZero() { return math.ZeroInt(), math.ZeroInt() } @@ -177,7 +175,6 @@ func calcAutoswapAmount(params depositTestParams) (swapAmountA math.Int, swapAmo amountSwapped0 := amountSwappedAs1.Quo(price1To0) return amountSwapped0.Ceil().TruncateInt(), math.ZeroInt() } - } func calcExpectedDepositAmounts(params depositTestParams) (tokenAAmount, tokenBAmount, sharesIssued math.Int) { @@ -198,28 +195,25 @@ func calcExpectedDepositAmounts(params depositTestParams) (tokenAAmount, tokenBA sharesIssued = depositValueAsToken0.Mul(shareValue).TruncateInt() return inAmountA, inAmountB, sharesIssued - } else { - autoSwapAmountA, autoswapAmountB := calcAutoswapAmount(params) - autoswapValueAsToken0 := calcDepositValueAsToken0(params.Tick, autoSwapAmountA, autoswapAmountB) - - autoswapFeeAsPrice := types.MustCalcPrice(-int64(params.Fee)) - autoswapFeePct := math_utils.OnePrecDec().Sub(autoswapFeeAsPrice) - autoswapFee := autoswapValueAsToken0.Mul(autoswapFeePct) + } // else + autoSwapAmountA, autoswapAmountB := calcAutoswapAmount(params) + autoswapValueAsToken0 := calcDepositValueAsToken0(params.Tick, autoSwapAmountA, autoswapAmountB) - inAmountA := params.DepositAmounts.TokenA.Amount - inAmountB := params.DepositAmounts.TokenB.Amount + autoswapFeeAsPrice := dextypes.MustCalcPrice(-int64(params.Fee)) + autoswapFeePct := math_utils.OnePrecDec().Sub(autoswapFeeAsPrice) + autoswapFee := autoswapValueAsToken0.Mul(autoswapFeePct) - fullDepositValueAsToken0 := calcDepositValueAsToken0(params.Tick, inAmountA, inAmountB) - depositAmountMinusFee := fullDepositValueAsToken0.Sub(autoswapFee) - currentValueWithAutoswapFee := existingValueAsToken0.Add(autoswapFee) - shareValue := calcCurrentShareValue(params, currentValueWithAutoswapFee) + inAmountA = params.DepositAmounts.TokenA.Amount + inAmountB = params.DepositAmounts.TokenB.Amount - sharesIssued = depositAmountMinusFee.Mul(shareValue).TruncateInt() + fullDepositValueAsToken0 := calcDepositValueAsToken0(params.Tick, inAmountA, inAmountB) + depositAmountMinusFee := fullDepositValueAsToken0.Sub(autoswapFee) + currentValueWithAutoswapFee := existingValueAsToken0.Add(autoswapFee) + shareValue := calcCurrentShareValue(params, currentValueWithAutoswapFee) - return inAmountA, inAmountB, sharesIssued - - } + sharesIssued = depositAmountMinusFee.Mul(shareValue).TruncateInt() + return inAmountA, inAmountB, sharesIssued } func (s *DexStateTestSuite) handleBaseFailureCases(params depositTestParams, err error) { diff --git a/tests/dex/state_place_limit_order_maker_test.go b/tests/dex/state_place_limit_order_maker_test.go index c6eee2d01..fa046e83a 100644 --- a/tests/dex/state_place_limit_order_maker_test.go +++ b/tests/dex/state_place_limit_order_maker_test.go @@ -290,7 +290,7 @@ func TestPlaceLimitOrderMaker(t *testing.T) { } expectedSwapTakerDenom := s.expectedInOutTokensAmount(amountIn, tc.PairID.Token1) totalExpectedToSwap = totalExpectedToSwap.Add(expectedSwapTakerDenom) - resp, err := s.makePlaceLO(s.creator, amountIn, tc.PairID.Token1, DefaultSellPrice, dextypes.LimitOrderType(tc.OrderType), expTime) + resp, err := s.makePlaceLO(s.creator, amountIn, tc.PairID.Token1, DefaultSellPrice, tc.OrderType, expTime) s.Require().NoError(err) // 1. generic liquidity check assertion diff --git a/tests/dex/state_place_limit_order_taker_test.go b/tests/dex/state_place_limit_order_taker_test.go index a04b217c8..372a42ff8 100644 --- a/tests/dex/state_place_limit_order_taker_test.go +++ b/tests/dex/state_place_limit_order_taker_test.go @@ -193,7 +193,8 @@ func ExpectedInOut(params placeLimitOrderTakerTestParams) (math.Int, math.Int) { availableLiquidity := tickLiquidity outGivenIn := math_utils.NewPrecDecFromInt(remainingIn).Quo(price).TruncateInt() - amountOut := math.ZeroInt() + var amountOut math.Int + var amountIn math.Int if params.MaxAmountOut != nil { maxAmountOut := params.MaxAmountOut.Sub(TotalOut) if !maxAmountOut.IsPositive() { @@ -202,12 +203,10 @@ func ExpectedInOut(params placeLimitOrderTakerTestParams) (math.Int, math.Int) { amountOut = utils.MinIntArr([]math.Int{availableLiquidity, outGivenIn, maxAmountOut}) } else { amountOut = utils.MinIntArr([]math.Int{availableLiquidity, outGivenIn}) - } amountInRaw := price.MulInt(amountOut) - amountIn := math.ZeroInt() if params.LiquidityType == LOLP { // Simulate rounding on two different tickLiquidities amountIn = amountInRaw.QuoInt(math.NewInt(2)).Ceil().TruncateInt().MulRaw(2) @@ -263,7 +262,7 @@ func TestPlaceLimitOrderTaker(t *testing.T) { s.fundCreatorBalanceDefault(tc.PairID) // - resp, err := s.makePlaceTakerLO(s.creator, tc.AmountIn, tc.PairID.Token0, tc.LimitPrice.String(), dextypes.LimitOrderType(tc.OrderType), tc.MaxAmountOut) + resp, err := s.makePlaceTakerLO(s.creator, tc.AmountIn, tc.PairID.Token0, tc.LimitPrice.String(), tc.OrderType, tc.MaxAmountOut) s.handleTakerErrors(tc, err) diff --git a/tests/dex/state_withdraw_limit_order_test.go b/tests/dex/state_withdraw_limit_order_test.go index 41b47d2ff..ab797546d 100644 --- a/tests/dex/state_withdraw_limit_order_test.go +++ b/tests/dex/state_withdraw_limit_order_test.go @@ -235,5 +235,4 @@ func TestWithdrawLimitOrder(t *testing.T) { */ }) } - }