From aa4d675618ee5706d29a76c1d34f7d1596d46be6 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 12 Sep 2023 13:34:01 -0400 Subject: [PATCH] refactor(CL): change full range definition --- x/concentrated-liquidity/fuzz_test.go | 7 +++- x/concentrated-liquidity/incentives_test.go | 2 +- x/concentrated-liquidity/keeper_test.go | 14 +++++-- x/concentrated-liquidity/lp_test.go | 8 ++-- x/concentrated-liquidity/math/math.go | 6 +-- x/concentrated-liquidity/model/pool.go | 4 +- x/concentrated-liquidity/model/pool_test.go | 8 ++-- x/concentrated-liquidity/pool_test.go | 2 +- x/concentrated-liquidity/position.go | 6 +-- x/concentrated-liquidity/position_test.go | 12 +++--- x/concentrated-liquidity/query.go | 6 +-- .../simulation/sim_msgs.go | 2 +- .../spread_rewards_test.go | 8 ++-- x/concentrated-liquidity/swaps.go | 24 +++++++----- x/concentrated-liquidity/swaps_test.go | 37 +++++++++++++++---- .../swaps_tick_cross_test.go | 16 ++++---- .../swapstrategy/swap_strategy.go | 6 +-- .../swapstrategy/swap_strategy_test.go | 8 ++-- .../swapstrategy/zero_for_one.go | 4 +- x/protorev/keeper/epoch_hook_test.go | 16 +++++++- x/superfluid/keeper/concentrated_liquidity.go | 2 +- 21 files changed, 125 insertions(+), 73 deletions(-) diff --git a/x/concentrated-liquidity/fuzz_test.go b/x/concentrated-liquidity/fuzz_test.go index acd3d821bb4..c6bf8548be7 100644 --- a/x/concentrated-liquidity/fuzz_test.go +++ b/x/concentrated-liquidity/fuzz_test.go @@ -46,7 +46,12 @@ func TestFuzz_Many(t *testing.T) { func (s *KeeperTestSuite) TestFuzz_GivenSeed() { // Seed 1688572291 - gives mismatch between tokenIn given to "out given in" and token in returned from "in given out" // Seed 1688658883- causes an error in swap in given out due to rounding (acceptable). - r := rand.New(rand.NewSource(1688658883)) + // Seed 1694529692 - proves issues in rounding direction of GetNextSqrtPriceFromAmount0InRoundingUp, proving + // that we must not QuoRoundUp for the last term in the formula. + // To repro: set up a breakpoint at the top of computeSwapOutGivenIn, and set count to 15. + // On the third (last) swap step for that swap, observe that the final `sqrtPriceNext` is off by one + // BigDec ULP, showing that this rounding bheavior was the issue. + r := rand.New(rand.NewSource(1694529692)) s.individualFuzz(r, 0, 30, 10) s.validateNoErrors(s.collectedErrors) diff --git a/x/concentrated-liquidity/incentives_test.go b/x/concentrated-liquidity/incentives_test.go index 0ae08536c8c..5c875adbba9 100644 --- a/x/concentrated-liquidity/incentives_test.go +++ b/x/concentrated-liquidity/incentives_test.go @@ -3556,7 +3556,7 @@ func (s *KeeperTestSuite) TestGetIncentiveRecordSerialized() { func (s *KeeperTestSuite) TestCollectIncentives_MinSpotPriceMigration() { s.SetupTest() - incentiveAmount := osmomath.NewInt(1000) + incentiveAmount := osmomath.NewInt(1_000_000_000) incentiveCoin := sdk.NewCoin(OSMO, incentiveAmount) expectedTotalIncentiveRewards := sdk.NewCoins(incentiveCoin) _, positions, _ := s.swapToMinTickAndBack(osmomath.ZeroDec(), expectedTotalIncentiveRewards) diff --git a/x/concentrated-liquidity/keeper_test.go b/x/concentrated-liquidity/keeper_test.go index 7e67b5253e8..87be249d52c 100644 --- a/x/concentrated-liquidity/keeper_test.go +++ b/x/concentrated-liquidity/keeper_test.go @@ -36,8 +36,8 @@ var ( // DefaultMinTick to tyoes.MinInitializedTickV2 and // DefaultMinCurrentTick to types.MinCurrentTickV2 upon // completion of https://github.com/osmosis-labs/osmosis/issues/5726 - DefaultMinTick, DefaultMaxTick = types.MinInitializedTick, types.MaxTick - DefaultMinCurrentTick = types.MinCurrentTick + DefaultMinTick, DefaultMaxTick = types.MinInitializedTickV2, types.MaxTick + DefaultMinCurrentTick = types.MinCurrentTickV2 DefaultLowerPrice = osmomath.NewDec(4545) DefaultLowerTick = int64(30545000) DefaultUpperPrice = osmomath.NewDec(5500) @@ -523,6 +523,9 @@ func (s *KeeperTestSuite) swapToMinTickAndBack(spreadFactor osmomath.Dec, incent incentiveCreator := s.TestAccs[2] s.FundAcc(incentiveCreator, incentiveRewards) + // By default we want to swap all the way to MinInitializedTickV2 + shouldCrossTick := true + // Create incentive rewards if desired if !incentiveRewards.Empty() { s.Require().Len(incentiveRewards, 1) @@ -532,10 +535,15 @@ func (s *KeeperTestSuite) swapToMinTickAndBack(spreadFactor osmomath.Dec, incent s.Ctx, poolId, s.TestAccs[2], incentiveCoin, incentiveCoin.Amount.ToLegacyDec().Quo(osmomath.NewDec(migrationTestTimeBetweenSwapsSecs)), s.Ctx.BlockTime(), time.Nanosecond) s.Require().NoError(err) + + // With incentive tests, we do not want to cross the MinInitializedTick. + // If that happens, the liquidity in the current tick is zero. As a result, + // the incentive distirbution fails. + shouldCrossTick = false } // esimate amount in to swap left all the way until the new min initialized tick - amountZeroIn, _, _ := s.computeSwapAmounts(poolId, pool.GetCurrentSqrtPrice(), types.MinInitializedTickV2, true, false) + amountZeroIn, _, _ := s.computeSwapAmounts(poolId, pool.GetCurrentSqrtPrice(), types.MinInitializedTickV2, true, !shouldCrossTick) // Fund swapper swapper := s.TestAccs[1] diff --git a/x/concentrated-liquidity/lp_test.go b/x/concentrated-liquidity/lp_test.go index a09e966e203..2525c682138 100644 --- a/x/concentrated-liquidity/lp_test.go +++ b/x/concentrated-liquidity/lp_test.go @@ -598,8 +598,8 @@ func (s *KeeperTestSuite) TestWithdrawPosition() { // * liquidity = FullRangeLiquidityAmt // * sqrtPriceB = DefaultCurrSqrtPrice // * sqrtPriceA = MinSqrtPrice - // Exact calculation: https://www.wolframalpha.com/input?i=70710678.118654752940000000+*+%2870.710678118654752440+-+0.000001000000000000%29 - amount1Expected: osmomath.NewInt(4999999929), + // Exact calculation: https://www.wolframalpha.com/input?i=70710678.118654752940000000+*+%2870.710678118654752440+-+0.000000000000001%29 + amount1Expected: osmomath.NewInt(4999999999), liquidityAmount: FullRangeLiquidityAmt, underlyingLockId: 1, }, @@ -1028,8 +1028,8 @@ func (s *KeeperTestSuite) TestAddToPosition() { // We calculate calc amount1 by using the following equation: // liq * (sqrtPriceB - sqrtPriceA), where liq is equal to the original joined liq + added liq, sqrtPriceB is current sqrt price, and sqrtPriceA is min sqrt price. // Note that these numbers were calculated using `GetLiquidityFromAmounts` and `TickToSqrtPrice` and thus assume correctness of those functions. - // https://www.wolframalpha.com/input?i=212041526.154556192317664016+*+%2870.728769315114743566+-+0.000001000000000000%29 - amount1Expected: osmomath.NewInt(14997435977), + // https://www.wolframalpha.com/input?i=212041526.154556192317664016+*+%2870.728769315114743566+-+0.000000000000001000%29 + amount1Expected: osmomath.NewInt(14997436189), }, timeElapsed: defaultTimeElapsed, amount0ToAdd: amount0PerfectRatio, diff --git a/x/concentrated-liquidity/math/math.go b/x/concentrated-liquidity/math/math.go index d24ab58b110..fa547b0a6c8 100644 --- a/x/concentrated-liquidity/math/math.go +++ b/x/concentrated-liquidity/math/math.go @@ -73,13 +73,13 @@ func CalcAmount0Delta(liq, sqrtPriceA, sqrtPriceB osmomath.BigDec, roundUp bool) // - calculating amountIn during swap // - adding liquidity (request user to provide more tokens in in favor of the pool) // The denominator is truncated to get a higher final amount. - return liq.MulRoundUp(diff).QuoRoundUp(sqrtPriceA).QuoRoundUp(sqrtPriceB).Ceil() + return liq.MulRoundUp(diff).QuoRoundUp(sqrtPriceB).QuoRoundUp(sqrtPriceA).Ceil() } // These are truncated at precision end to round in favor of the pool when: // - calculating amount out during swap // - withdrawing liquidity // Each intermediary step is truncated at precision end to get a smaller final amount. - return liq.MulTruncate(diff).QuoTruncate(sqrtPriceA).QuoTruncate(sqrtPriceB) + return liq.MulTruncate(diff).QuoTruncate(sqrtPriceB).QuoTruncate(sqrtPriceA) } // CalcAmount1Delta takes the asset with the smaller liquidity in the pool as well as the sqrtpCur and the nextPrice and calculates the amount of asset 1 @@ -124,7 +124,7 @@ func GetNextSqrtPriceFromAmount0InRoundingUp(sqrtPriceCurrent, liquidity, amount return sqrtPriceCurrent } - product := amountZeroRemainingIn.Mul(sqrtPriceCurrent) + product := amountZeroRemainingIn.MulTruncate(sqrtPriceCurrent) // denominator = product + liquidity denominator := product denominator.AddMut(liquidity) diff --git a/x/concentrated-liquidity/model/pool.go b/x/concentrated-liquidity/model/pool.go index eabd404869c..51e3196c427 100644 --- a/x/concentrated-liquidity/model/pool.go +++ b/x/concentrated-liquidity/model/pool.go @@ -295,10 +295,10 @@ func (p *Pool) ApplySwap(newLiquidity osmomath.Dec, newCurrentTick int64, newCur } // Check if the new tick provided is within boundaries of the pool's precision factor. - if newCurrentTick < types.MinCurrentTick || newCurrentTick > types.MaxTick { + if newCurrentTick < types.MinCurrentTickV2 || newCurrentTick > types.MaxTick { return types.TickIndexNotWithinBoundariesError{ MaxTick: types.MaxTick, - MinTick: types.MinCurrentTick, + MinTick: types.MinCurrentTickV2, ActualTick: newCurrentTick, } } diff --git a/x/concentrated-liquidity/model/pool_test.go b/x/concentrated-liquidity/model/pool_test.go index 08f73e12c60..cd84a9be023 100644 --- a/x/concentrated-liquidity/model/pool_test.go +++ b/x/concentrated-liquidity/model/pool_test.go @@ -384,7 +384,7 @@ func (s *ConcentratedPoolTestSuite) TestApplySwap() { currentTick: DefaultCurrTick, currentSqrtPrice: DefaultCurrSqrtPrice, newLiquidity: DefaultLiquidityAmt, - newTick: types.MinInitializedTick, + newTick: types.MinInitializedTickV2, newSqrtPrice: DefaultCurrSqrtPrice, expectErr: nil, }, @@ -394,7 +394,7 @@ func (s *ConcentratedPoolTestSuite) TestApplySwap() { currentTick: DefaultCurrTick, currentSqrtPrice: DefaultCurrSqrtPrice, newLiquidity: DefaultLiquidityAmt, - newTick: types.MinCurrentTick, + newTick: types.MinCurrentTickV2, newSqrtPrice: DefaultCurrSqrtPrice, expectErr: nil, }, @@ -408,7 +408,7 @@ func (s *ConcentratedPoolTestSuite) TestApplySwap() { newSqrtPrice: DefaultCurrSqrtPrice, expectErr: types.TickIndexNotWithinBoundariesError{ MaxTick: types.MaxTick, - MinTick: types.MinCurrentTick, + MinTick: types.MinCurrentTickV2, ActualTick: math.MaxInt64, }, }, @@ -422,7 +422,7 @@ func (s *ConcentratedPoolTestSuite) TestApplySwap() { newSqrtPrice: DefaultCurrSqrtPrice, expectErr: types.TickIndexNotWithinBoundariesError{ MaxTick: types.MaxTick, - MinTick: types.MinCurrentTick, + MinTick: types.MinCurrentTickV2, ActualTick: math.MinInt64, }, }, diff --git a/x/concentrated-liquidity/pool_test.go b/x/concentrated-liquidity/pool_test.go index 80cd46917a4..d8dc81d064e 100644 --- a/x/concentrated-liquidity/pool_test.go +++ b/x/concentrated-liquidity/pool_test.go @@ -649,7 +649,7 @@ func (s *KeeperTestSuite) TestGetUserUnbondingPositions() { PositionId: 3, Address: defaultAddress.String(), PoolId: 1, - LowerTick: types.MinInitializedTick, + LowerTick: types.MinInitializedTickV2, UpperTick: types.MaxTick, JoinTime: defaultBlockTime, Liquidity: osmomath.MustNewDecFromStr("10000.000000000000001000"), diff --git a/x/concentrated-liquidity/position.go b/x/concentrated-liquidity/position.go index 0f4844cb41a..75b325126f5 100644 --- a/x/concentrated-liquidity/position.go +++ b/x/concentrated-liquidity/position.go @@ -329,7 +329,7 @@ func (k Keeper) SetPosition(ctx sdk.Context, } // If position is full range, update the pool ID to total full range liquidity mapping. - if lowerTick == types.MinInitializedTick && upperTick == types.MaxTick { + if lowerTick == types.MinInitializedTickV2 && upperTick == types.MaxTick { err := k.updateFullRangeLiquidityInPool(ctx, poolId, liquidity) if err != nil { return err @@ -413,7 +413,7 @@ func (k Keeper) CreateFullRangePosition(ctx sdk.Context, poolId uint64, owner sd } // Create a full range (min to max tick) concentrated liquidity position. - positionData, err := k.CreatePosition(ctx, concentratedPool.GetId(), owner, coins, osmomath.ZeroInt(), osmomath.ZeroInt(), types.MinInitializedTick, types.MaxTick) + positionData, err := k.CreatePosition(ctx, concentratedPool.GetId(), owner, coins, osmomath.ZeroInt(), osmomath.ZeroInt(), types.MinInitializedTickV2, types.MaxTick) if err != nil { return types.CreateFullRangePositionData{}, err } @@ -477,7 +477,7 @@ func (k Keeper) mintSharesAndLock(ctx sdk.Context, concentratedPoolId, positionI if err != nil { return 0, sdk.Coins{}, err } - if position.LowerTick != types.MinInitializedTick || position.UpperTick != types.MaxTick { + if position.LowerTick != types.MinInitializedTickV2 || position.UpperTick != types.MaxTick { return 0, sdk.Coins{}, types.PositionNotFullRangeError{PositionId: positionId, LowerTick: position.LowerTick, UpperTick: position.UpperTick} } diff --git a/x/concentrated-liquidity/position_test.go b/x/concentrated-liquidity/position_test.go index d50f0929c5d..b219203424a 100644 --- a/x/concentrated-liquidity/position_test.go +++ b/x/concentrated-liquidity/position_test.go @@ -1104,10 +1104,10 @@ func (s *KeeperTestSuite) TestMintSharesAndLock() { name: "err: upper tick is not max tick", owner: defaultAddress, createFullRangePosition: false, - lowerTick: types.MinInitializedTick, + lowerTick: types.MinInitializedTickV2, upperTick: DefaultUpperTick, remainingLockDuration: 24 * time.Hour, - expectedErr: types.PositionNotFullRangeError{PositionId: 1, LowerTick: types.MinInitializedTick, UpperTick: DefaultUpperTick}, + expectedErr: types.PositionNotFullRangeError{PositionId: 1, LowerTick: types.MinInitializedTickV2, UpperTick: DefaultUpperTick}, }, } @@ -1606,7 +1606,7 @@ func (s *KeeperTestSuite) TestGetAndUpdateFullRangeLiquidity() { { name: "full range + position overlapping min tick. update liquidity upwards", positionCoins: sdk.NewCoins(DefaultCoin0, DefaultCoin1), - lowerTick: DefaultMinTick, + lowerTick: types.MinInitializedTickV2, upperTick: DefaultUpperTick, // max tick doesn't overlap, should not count towards full range liquidity updateLiquidity: osmomath.NewDec(100), }, @@ -1764,7 +1764,7 @@ func (s *KeeperTestSuite) TestCreateFullRangePositionLocked() { s.Require().NoError(err) s.Require().Equal(s.Ctx.BlockTime(), position.JoinTime) s.Require().Equal(types.MaxTick, position.UpperTick) - s.Require().Equal(types.MinInitializedTick, position.LowerTick) + s.Require().Equal(types.MinInitializedTickV2, position.LowerTick) s.Require().Equal(positionData.Liquidity, position.Liquidity) // Check locked @@ -1926,7 +1926,7 @@ func (s *KeeperTestSuite) TestMultipleRanges() { }, "one range on min tick": { tickRanges: [][]int64{ - {types.MinInitializedTick, types.MinInitializedTick + 100}, + {types.MinInitializedTickV2, types.MinInitializedTickV2 + 100}, }, rangeTestParams: withDoubleFundedLP(DefaultRangeTestParams), }, @@ -1934,7 +1934,7 @@ func (s *KeeperTestSuite) TestMultipleRanges() { tickRanges: [][]int64{ {0, 1}, }, - rangeTestParams: withCurrentTick(DefaultRangeTestParams, types.MinInitializedTick), + rangeTestParams: withCurrentTick(DefaultRangeTestParams, types.MinInitializedTickV2), }, "three overlapping ranges with no swaps, current tick in one": { tickRanges: [][]int64{ diff --git a/x/concentrated-liquidity/query.go b/x/concentrated-liquidity/query.go index 611a686e6b4..b3b578187fc 100644 --- a/x/concentrated-liquidity/query.go +++ b/x/concentrated-liquidity/query.go @@ -27,7 +27,7 @@ func (k Keeper) GetTickLiquidityForFullRange(ctx sdk.Context, poolId uint64) ([] // set current tick to min tick, and find the first initialized tick starting from min tick -1. // we do -1 to make min tick inclusive. - currentTick := types.MinCurrentTick + currentTick := types.MinCurrentTickV2 nextTickIter := swapStrategy.InitializeNextTickIterator(ctx, poolId, currentTick) defer nextTickIter.Close() @@ -137,12 +137,12 @@ func (k Keeper) GetTickLiquidityNetInDirection(ctx sdk.Context, poolId uint64, t // use max or min tick if provided bound is nil - ctx.Logger().Debug(fmt.Sprintf("min_tick %d\n", types.MinInitializedTick)) + ctx.Logger().Debug(fmt.Sprintf("min_tick %d\n", types.MinInitializedTickV2)) ctx.Logger().Debug(fmt.Sprintf("max_tick %d\n", types.MaxTick)) if boundTick.IsNil() { if zeroForOne { - boundTick = osmomath.NewInt(types.MinInitializedTick) + boundTick = osmomath.NewInt(types.MinInitializedTickV2) } else { boundTick = osmomath.NewInt(types.MaxTick) } diff --git a/x/concentrated-liquidity/simulation/sim_msgs.go b/x/concentrated-liquidity/simulation/sim_msgs.go index df8f3ab63fa..040706beec2 100644 --- a/x/concentrated-liquidity/simulation/sim_msgs.go +++ b/x/concentrated-liquidity/simulation/sim_msgs.go @@ -399,7 +399,7 @@ func RandomPrepareCreatePositionFunc(sim *osmosimtypes.SimCtx, ctx sdk.Context, } // Retrieve minTick and maxTick from kprecision factor - minTick, maxTick := cltypes.MinInitializedTick, cltypes.MaxTick + minTick, maxTick := cltypes.MinInitializedTickV2, cltypes.MaxTick // Randomize lowerTick and upperTick from max values to create position lowerTick, upperTick, err := getRandomTickPositions(sim, minTick, maxTick, clPool.GetTickSpacing()) diff --git a/x/concentrated-liquidity/spread_rewards_test.go b/x/concentrated-liquidity/spread_rewards_test.go index 37e5bd86d92..1a5add52830 100644 --- a/x/concentrated-liquidity/spread_rewards_test.go +++ b/x/concentrated-liquidity/spread_rewards_test.go @@ -1375,12 +1375,12 @@ func (s *KeeperTestSuite) TestFunctional_SpreadRewards_Swaps() { s.CollectAndAssertSpreadRewards(s.Ctx, clPool.GetId(), totalSpreadRewardsExpected, positionIds, [][]int64{ticksActivatedAfterEachSwap}, onlyUSDC, positions) // Swap multiple times ETH for USDC, therefore decreasing the spot price - ticksActivatedAfterEachSwap, totalSpreadRewardsExpected, _, _ = s.swapAndTrackXTimesInARow(clPool.GetId(), DefaultCoin0, USDC, types.MinSpotPriceBigDec, positions.numSwaps) + ticksActivatedAfterEachSwap, totalSpreadRewardsExpected, _, _ = s.swapAndTrackXTimesInARow(clPool.GetId(), DefaultCoin0, USDC, types.MinSpotPriceV2, positions.numSwaps) s.CollectAndAssertSpreadRewards(s.Ctx, clPool.GetId(), totalSpreadRewardsExpected, positionIds, [][]int64{ticksActivatedAfterEachSwap}, onlyETH, positions) // Do the same swaps as before, however this time we collect spread rewards after both swap directions are complete. ticksActivatedAfterEachSwapUp, totalSpreadRewardsExpectedUp, _, _ := s.swapAndTrackXTimesInARow(clPool.GetId(), DefaultCoin1, ETH, types.MaxSpotPriceBigDec, positions.numSwaps) - ticksActivatedAfterEachSwapDown, totalSpreadRewardsExpectedDown, _, _ := s.swapAndTrackXTimesInARow(clPool.GetId(), DefaultCoin0, USDC, types.MinSpotPriceBigDec, positions.numSwaps) + ticksActivatedAfterEachSwapDown, totalSpreadRewardsExpectedDown, _, _ := s.swapAndTrackXTimesInARow(clPool.GetId(), DefaultCoin0, USDC, types.MinSpotPriceV2, positions.numSwaps) totalSpreadRewardsExpected = totalSpreadRewardsExpectedUp.Add(totalSpreadRewardsExpectedDown...) // We expect all positions to have both denoms in their spread reward accumulators except USDC for the overlapping range position since @@ -1447,7 +1447,7 @@ func (s *KeeperTestSuite) TestFunctional_SpreadRewards_LP() { fullLiquidity := positionDataTwo.Liquidity // Swap once in the other direction. - ticksActivatedAfterEachSwap, totalSpreadRewardsExpected, _, _ = s.swapAndTrackXTimesInARow(pool.GetId(), DefaultCoin0, USDC, types.MinSpotPriceBigDec, 1) + ticksActivatedAfterEachSwap, totalSpreadRewardsExpected, _, _ = s.swapAndTrackXTimesInARow(pool.GetId(), DefaultCoin0, USDC, types.MinSpotPriceV2, 1) // This should claim under the hood for position 2 since full liquidity is removed. balanceBeforeWithdraw := s.App.BankKeeper.GetBalance(ctx, owner, ETH) @@ -1515,7 +1515,7 @@ func (s *KeeperTestSuite) TestCollectSpreadRewards_MinSpotPriceMigration() { // Validate that the total spread rewards collected is equal to the expected total spread rewards s.Require().Equal(len(expectedTotalSpreadRewards), len(actualCollected)) for _, coin := range expectedTotalSpreadRewards { - osmoassert.Equal(s.T(), oneAdditiveTolerance, coin.Amount, actualCollected.AmountOf(coin.Denom)) + osmoassert.Equal(s.T(), multiplicativeTolerance, coin.Amount, actualCollected.AmountOf(coin.Denom)) } } diff --git a/x/concentrated-liquidity/swaps.go b/x/concentrated-liquidity/swaps.go index 8110400dc79..84e4cfda0c3 100644 --- a/x/concentrated-liquidity/swaps.go +++ b/x/concentrated-liquidity/swaps.go @@ -75,7 +75,7 @@ type SwapResult struct { // Note, the value is chosen arbitrarily. // From tests, there should be no reason for a swap to make more than 2 iterations without // progress. However, we leave a buffer of 1_000 to account for any unforeseen edge cases. -const swapNoProgressLimit = 100 +const swapNoProgressLimit = 5 func newSwapState(specifiedAmount osmomath.Int, p types.ConcentratedPoolExtension, strategy swapstrategy.SwapStrategy) SwapState { return SwapState{ @@ -514,6 +514,10 @@ func (k Keeper) computeInAmtGivenOut( return SwapResult{}, PoolUpdates{}, err } + fmt.Printf("\nswap step begin\n") + fmt.Println("start tick", swapState.tick) + fmt.Println("nextInitializedTick", nextInitializedTick) + // Utilizing the bucket's liquidity and knowing the sqrt price target, we calculate the how much tokenOut we get from the tokenIn // we also calculate the swap state's new computedSqrtPrice after this swap computedSqrtPrice, amountOut, amountIn, spreadRewardChargeTotal := swapStrategy.ComputeSwapWithinBucketInGivenOut( @@ -584,8 +588,10 @@ func (k Keeper) computeInAmtGivenOut( // Round amount out down to avoid over charging the pool. amountOut := desiredTokenOut.Amount.ToLegacyDec().Sub(swapState.amountSpecifiedRemaining).TruncateInt() - ctx.Logger().Debug("final amount in", amountIn) - ctx.Logger().Debug("final amount out", amountOut) + fmt.Println("final amount in", amountIn) + fmt.Println("final amount out", amountOut) + + fmt.Println("final tick", swapState.tick) return SwapResult{ AmountIn: amountIn, @@ -595,12 +601,12 @@ func (k Keeper) computeInAmtGivenOut( } func emitSwapDebugLogs(ctx sdk.Context, swapState SwapState, reachedPrice osmomath.BigDec, amountIn, amountOut, spreadCharge osmomath.Dec) { - ctx.Logger().Debug("start sqrt price", swapState.sqrtPrice) - ctx.Logger().Debug("reached sqrt price", reachedPrice) - ctx.Logger().Debug("liquidity", swapState.liquidity) - ctx.Logger().Debug("amountIn", amountIn) - ctx.Logger().Debug("amountOut", amountOut) - ctx.Logger().Debug("spreadRewardChargeTotal", spreadCharge) + fmt.Println("start sqrt price", swapState.sqrtPrice) + fmt.Println("reached sqrt price", reachedPrice) + fmt.Println("liquidity", swapState.liquidity) + fmt.Println("amountIn", amountIn) + fmt.Println("amountOut", amountOut) + fmt.Println("spreadRewardChargeTotal", spreadCharge) } // logic for crossing a tick during a swap diff --git a/x/concentrated-liquidity/swaps_test.go b/x/concentrated-liquidity/swaps_test.go index 87f343d086e..b5c604abbee 100644 --- a/x/concentrated-liquidity/swaps_test.go +++ b/x/concentrated-liquidity/swaps_test.go @@ -3235,7 +3235,7 @@ func (s *KeeperTestSuite) TestFunctionalSwaps() { } // Swap multiple times ETH for USDC, therefore decreasing the spot price - _, _, totalTokenIn, totalTokenOut = s.swapAndTrackXTimesInARow(clPool.GetId(), swapCoin0, USDC, types.MinSpotPriceBigDec, positions.numSwaps) + _, _, totalTokenIn, totalTokenOut = s.swapAndTrackXTimesInARow(clPool.GetId(), swapCoin0, USDC, types.MinSpotPriceV2, positions.numSwaps) clPool, err = s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, clPool.GetId()) s.Require().NoError(err) @@ -3355,7 +3355,7 @@ func (s *KeeperTestSuite) TestFunctionalSwaps() { } // Swap multiple times ETH for USDC, therefore decreasing the spot price - _, _, totalTokenIn, totalTokenOut = s.swapAndTrackXTimesInARow(clPool.GetId(), swapCoin0, USDC, types.MinSpotPriceBigDec, positions.numSwaps) + _, _, totalTokenIn, totalTokenOut = s.swapAndTrackXTimesInARow(clPool.GetId(), swapCoin0, USDC, types.MinSpotPriceV2, positions.numSwaps) clPool, err = s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, clPool.GetId()) s.Require().NoError(err) @@ -3535,14 +3535,23 @@ func (s *KeeperTestSuite) TestSwap_MinSpotPriceMigration() { originalTick := pool.GetCurrentTick() // esimate amount in to swap left all the way until the new min initialized tick - amountOneOut, _, _ := s.computeSwapAmountsInGivenOut(poolId, pool.GetCurrentSqrtPrice(), types.MinInitializedTick, true, false) + amountOneOut, _, _ := s.computeSwapAmountsInGivenOut(poolId, pool.GetCurrentSqrtPrice(), types.MinInitializedTickV2, true, false) // estimate the amount in to fund - amountZeroIn, _, _ := s.computeSwapAmounts(poolId, pool.GetCurrentSqrtPrice(), types.MinInitializedTick, true, false) + amountZeroIn, _, _ := s.computeSwapAmounts(poolId, pool.GetCurrentSqrtPrice(), types.MinInitializedTickV2, true, false) + + fmt.Println("estimated amountOneOut", amountOneOut) + fmt.Println("estimated amountZeroIn", amountZeroIn) + + fmt.Println("token one before", s.App.BankKeeper.GetBalance(s.Ctx, s.TestAccs[1], pool.GetToken1())) // Fund swapper swapper := s.TestAccs[1] - s.FundAcc(swapper, sdk.NewCoins(sdk.NewCoin(pool.GetToken0(), amountZeroIn.TruncateInt()))) + s.FundAcc(swapper, sdk.NewCoins(sdk.NewCoin(pool.GetToken0(), amountZeroIn.Ceil().TruncateInt()))) + + // zero (in) for given one (out) + + fmt.Printf("SWAP ONE\n\n\n") // perform the swap to the new min initialized tick. coinOneOut := sdk.NewCoin(pool.GetToken1(), amountOneOut.TruncateInt()) @@ -3553,27 +3562,39 @@ func (s *KeeperTestSuite) TestSwap_MinSpotPriceMigration() { ) s.Require().NoError(err) + fmt.Printf("\n\n") + + fmt.Println("actual tokenZeroIn", tokenZeroIn) + + fmt.Println("token one after", s.App.BankKeeper.GetBalance(s.Ctx, s.TestAccs[1], pool.GetToken1())) + // Refetch pool pool, err = s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, poolId) s.Require().NoError(err) // Confirm all liquidity was consumed and `MinCurrentTick` set - s.Require().Equal(types.MinCurrentTick, pool.GetCurrentTick()) + // TODO: consider adding this to setupPositionsForMinSpotPriceMigration + s.Require().Equal(types.MinCurrentTickV2, pool.GetCurrentTick()) // Esimate the amount in that needs to be funded due to rounding differences. amountOneIn, _, _ := s.computeSwapAmounts(poolId, pool.GetCurrentSqrtPrice(), originalTick, false, false) s.FundAcc(swapper, sdk.NewCoins(sdk.NewCoin(pool.GetToken1(), amountOneIn.Ceil().TruncateInt().Sub(tokenOneOut.Amount)))) // Swap amount out to the end up in the original tick - inverseTokenOut, _, _, err := s.App.ConcentratedLiquidityKeeper.SwapInAmtGivenOut( + tokenOneIn, _, _, err := s.App.ConcentratedLiquidityKeeper.SwapInAmtGivenOut( s.Ctx, swapper, pool, tokenZeroIn, pool.GetToken1(), osmomath.ZeroDec(), osmomath.ZeroBigDec(), ) s.Require().NoError(err) + pool, err = s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, poolId) + s.Require().NoError(err) + + osmoassert.Equal(s.T(), multiplicativeTolerance, osmomath.NewInt(originalTick), osmomath.NewInt(pool.GetCurrentTick())) + // Original amount in should roughly equal the amount out when performing the inverse swap - osmoassert.Equal(s.T(), multiplicativeTolerance, coinOneOut.Amount, inverseTokenOut.Amount) + osmoassert.Equal(s.T(), multiplicativeTolerance, coinOneOut.Amount, tokenOneIn.Amount) }) } diff --git a/x/concentrated-liquidity/swaps_tick_cross_test.go b/x/concentrated-liquidity/swaps_tick_cross_test.go index a6dfcc2733a..3a3f323465d 100644 --- a/x/concentrated-liquidity/swaps_tick_cross_test.go +++ b/x/concentrated-liquidity/swaps_tick_cross_test.go @@ -74,7 +74,7 @@ func (s *KeeperTestSuite) validateIteratorLeftZeroForOne(poolId uint64, expected pool, err := s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, poolId) s.Require().NoError(err) - zeroForOneSwapStrategy, _, err := s.App.ConcentratedLiquidityKeeper.SetupSwapStrategy(s.Ctx, pool, osmomath.ZeroDec(), pool.GetToken0(), types.MinSqrtPriceBigDec) + zeroForOneSwapStrategy, _, err := s.App.ConcentratedLiquidityKeeper.SetupSwapStrategy(s.Ctx, pool, osmomath.ZeroDec(), pool.GetToken0(), types.MinSqrtPriceV2) s.Require().NoError(err) initializedTickValue := pool.GetCurrentTick() iter := zeroForOneSwapStrategy.InitializeNextTickIterator(s.Ctx, pool.GetId(), initializedTickValue) @@ -1038,10 +1038,10 @@ func (s *KeeperTestSuite) TestSwaps_Contiguous_Initialized_TickSpacingOne() { for i, swapTicksAway := range tc.swapEndTicksAwayFromOriginalCurrent { // We special case the min and max tick to be given by absolute values // rather than relative to the original current tick. - if swapTicksAway == types.MinInitializedTick { + if swapTicksAway == types.MinInitializedTickV2 { // In zero for one direction, we kick the tick back by one while crossing. // This is to ensure that our definition of "active bucket" is correct. - expectedSwapEndTicks[i] = types.MinCurrentTick + expectedSwapEndTicks[i] = types.MinCurrentTickV2 } else if swapTicksAway == types.MaxTick { expectedSwapEndTicks[i] = types.MaxTick } else { @@ -1105,7 +1105,7 @@ func (s *KeeperTestSuite) TestSwaps_Contiguous_Initialized_TickSpacingOne() { isPositionActiveFlag: []bool{true, true, true, false}, }, "zero for one, swap beyond the leftmost tick": { - swapEndTicksAwayFromOriginalCurrent: []int64{-3, types.MinInitializedTick}, + swapEndTicksAwayFromOriginalCurrent: []int64{-3, types.MinInitializedTickV2}, isPositionActiveFlag: []bool{false, false, false, false}, }, @@ -1334,17 +1334,17 @@ func (s *KeeperTestSuite) TestSwapOutGivenIn_SwapToAllowedBoundaries() { // Compute tokenIn amount necessary to reach the min tick. const isZeroForOne = true - tokenIn, _, _ := s.computeSwapAmounts(poolId, osmomath.BigDec{}, types.MinInitializedTick, isZeroForOne, shouldStayWithinTheSameBucket) + tokenIn, _, _ := s.computeSwapAmounts(poolId, osmomath.BigDec{}, types.MinInitializedTickV2, isZeroForOne, shouldStayWithinTheSameBucket) // Swap the computed large amount. s.swapZeroForOneLeft(poolId, sdk.NewCoin(tokenZeroDenom, tokenIn.Ceil().TruncateInt())) // Assert that min tick is crossed and liquidity is zero. - s.assertPoolTickEquals(poolId, types.MinCurrentTick) + s.assertPoolTickEquals(poolId, types.MinCurrentTickV2) s.assertPoolLiquidityEquals(poolId, osmomath.ZeroDec()) // Assert that full range positions are now inactive. - s.assertPositionOutOfRange(poolId, types.MinInitializedTick, types.MaxTick) + s.assertPositionOutOfRange(poolId, types.MinInitializedTickV2, types.MaxTick) // Refetch pool pool, err = s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, poolId) @@ -1387,7 +1387,7 @@ func (s *KeeperTestSuite) TestSwapOutGivenIn_SwapToAllowedBoundaries() { s.assertPoolLiquidityEquals(poolId, osmomath.ZeroDec()) // Assert that full range positions are now inactive. - s.assertPositionOutOfRange(poolId, types.MinInitializedTick, types.MaxTick) + s.assertPositionOutOfRange(poolId, types.MinInitializedTickV2, types.MaxTick) // Refetch pool pool, err = s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, poolId) diff --git a/x/concentrated-liquidity/swapstrategy/swap_strategy.go b/x/concentrated-liquidity/swapstrategy/swap_strategy.go index db4e6ba1ea6..4ba866acb03 100644 --- a/x/concentrated-liquidity/swapstrategy/swap_strategy.go +++ b/x/concentrated-liquidity/swapstrategy/swap_strategy.go @@ -106,7 +106,7 @@ func New(zeroForOne bool, sqrtPriceLimit osmomath.BigDec, storeKey sdk.StoreKey, // If one in for zero out, the price is increasing. Therefore, max spot price is the limit. func GetPriceLimit(zeroForOne bool) osmomath.BigDec { if zeroForOne { - return types.MinSpotPriceBigDec + return types.MinSpotPriceV2 } return types.MaxSpotPriceBigDec } @@ -123,9 +123,9 @@ func GetPriceLimit(zeroForOne bool) osmomath.BigDec { func GetSqrtPriceLimit(priceLimit osmomath.BigDec, zeroForOne bool) (osmomath.BigDec, error) { if priceLimit.IsZero() { if zeroForOne { - return types.MinSqrtPriceBigDec, nil + return types.MinSqrtPriceV2, nil } - return osmomath.BigDecFromDec(types.MaxSqrtPrice), nil + return types.MaxSqrtPriceBigDec, nil } if priceLimit.LT(types.MinSpotPriceV2) || priceLimit.GT(types.MaxSpotPriceBigDec) { diff --git a/x/concentrated-liquidity/swapstrategy/swap_strategy_test.go b/x/concentrated-liquidity/swapstrategy/swap_strategy_test.go index cc4b211b026..3567b1cf4c3 100644 --- a/x/concentrated-liquidity/swapstrategy/swap_strategy_test.go +++ b/x/concentrated-liquidity/swapstrategy/swap_strategy_test.go @@ -257,7 +257,7 @@ func (suite *StrategyTestSuite) TestGetPriceLimit() { }{ "zero for one -> min": { zeroForOne: true, - expected: types.MinSpotPriceBigDec, + expected: types.MinSpotPriceV2, }, "one for zero -> max": { zeroForOne: false, @@ -294,8 +294,8 @@ func (suite *StrategyTestSuite) TestGetSqrtPriceLimit() { }{ "zero for one -> min": { zeroForOne: true, - priceLimit: types.MinSpotPriceBigDec, - expected: types.MinSqrtPriceBigDec, + priceLimit: types.MinSpotPriceV2, + expected: types.MinSqrtPriceV2, }, "one for zero -> max": { zeroForOne: false, @@ -305,7 +305,7 @@ func (suite *StrategyTestSuite) TestGetSqrtPriceLimit() { "zero and zero for one -> min": { zeroForOne: true, priceLimit: zeroBigDec, - expected: types.MinSqrtPriceBigDec, + expected: types.MinSqrtPriceV2, }, "zero and one for zero -> max": { zeroForOne: false, diff --git a/x/concentrated-liquidity/swapstrategy/zero_for_one.go b/x/concentrated-liquidity/swapstrategy/zero_for_one.go index fe47dfe4904..f4d4dbb18f9 100644 --- a/x/concentrated-liquidity/swapstrategy/zero_for_one.go +++ b/x/concentrated-liquidity/swapstrategy/zero_for_one.go @@ -271,8 +271,8 @@ func (s zeroForOneStrategy) UpdateTickAfterCrossing(nextTick int64) int64 { // types.MinSqrtRatio <= sqrtPrice <= current square root price func (s zeroForOneStrategy) ValidateSqrtPrice(sqrtPrice osmomath.BigDec, currentSqrtPrice osmomath.BigDec) error { // check that the price limit is below the current sqrt price but not lower than the minimum sqrt price if we are swapping asset0 for asset1 - if sqrtPrice.GT(currentSqrtPrice) || sqrtPrice.LT(types.MinSqrtPriceBigDec) { - return types.SqrtPriceValidationError{SqrtPriceLimit: sqrtPrice, LowerBound: types.MinSqrtPriceBigDec, UpperBound: currentSqrtPrice} + if sqrtPrice.GT(currentSqrtPrice) || sqrtPrice.LT(types.MinSqrtPriceV2) { + return types.SqrtPriceValidationError{SqrtPriceLimit: sqrtPrice, LowerBound: types.MinSqrtPriceV2, UpperBound: currentSqrtPrice} } return nil } diff --git a/x/protorev/keeper/epoch_hook_test.go b/x/protorev/keeper/epoch_hook_test.go index d48fbf6a3c8..62f2f83d917 100644 --- a/x/protorev/keeper/epoch_hook_test.go +++ b/x/protorev/keeper/epoch_hook_test.go @@ -161,7 +161,7 @@ func (s *KeeperTestSuite) TestUpdateHighestLiquidityPools() { }, expectedBaseDenomPools: map[string]map[string]keeper.LiquidityPoolStruct{ "epochTwo": { - "uosmo": {Liquidity: osmomath.Int(osmomath.NewUintFromString("999999000000000001000000000000000000")), PoolId: 50}, + "uosmo": {Liquidity: osmomath.Int(osmomath.NewUintFromString("999999999999999001000000000000000000")), PoolId: 50}, }, }, }, @@ -176,7 +176,19 @@ func (s *KeeperTestSuite) TestUpdateHighestLiquidityPools() { err := s.App.ProtoRevKeeper.UpdateHighestLiquidityPools(s.Ctx, tc.inputBaseDenomPools) s.Require().NoError(err) - s.Require().Equal(tc.inputBaseDenomPools, tc.expectedBaseDenomPools) + + // Iterate over all expected epochs and all expected pools and compare them individually with the actual. + for epochKey, epoch := range tc.expectedBaseDenomPools { + actualEpoch, ok := tc.inputBaseDenomPools[epochKey] + s.Require().True(ok) + for poolKey, pool := range epoch { + actualPool, ok := actualEpoch[poolKey] + s.Require().True(ok) + + s.Require().Equal(pool.PoolId, actualPool.PoolId) + s.Require().Equal(pool.Liquidity.String(), actualPool.Liquidity.String()) + } + } }) } } diff --git a/x/superfluid/keeper/concentrated_liquidity.go b/x/superfluid/keeper/concentrated_liquidity.go index 649b0661a2f..ac8f7d38aff 100644 --- a/x/superfluid/keeper/concentrated_liquidity.go +++ b/x/superfluid/keeper/concentrated_liquidity.go @@ -50,7 +50,7 @@ func (k Keeper) addToConcentratedLiquiditySuperfluidPosition(ctx sdk.Context, se } // Defense in depth making sure that the position is full-range. - if position.LowerTick != cltypes.MinInitializedTick || position.UpperTick != cltypes.MaxTick { + if position.LowerTick != cltypes.MinInitializedTickV2 || position.UpperTick != cltypes.MaxTick { return cltypes.CreateFullRangePositionData{}, 0, types.ConcentratedTickRangeNotFullError{ActualLowerTick: position.LowerTick, ActualUpperTick: position.UpperTick} }