From 450a51aa104c0fa9737c1d3f0d9143c5aea7a82a Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 11 Sep 2023 12:16:56 -0400 Subject: [PATCH 01/10] refactor(CL): convert priceLimit API in swaps to BigDec --- x/concentrated-liquidity/export_test.go | 10 +- x/concentrated-liquidity/fuzz_test.go | 4 +- x/concentrated-liquidity/keeper_test.go | 4 +- x/concentrated-liquidity/position_test.go | 2 +- x/concentrated-liquidity/range_test.go | 4 +- .../spread_rewards_test.go | 16 +-- x/concentrated-liquidity/swaps.go | 16 +-- x/concentrated-liquidity/swaps_test.go | 118 +++++++++--------- .../swaps_tick_cross_test.go | 2 +- .../swapstrategy/swap_strategy.go | 31 +++-- .../swapstrategy/zero_for_one.go | 2 +- x/concentrated-liquidity/types/constants.go | 2 + 12 files changed, 115 insertions(+), 96 deletions(-) diff --git a/x/concentrated-liquidity/export_test.go b/x/concentrated-liquidity/export_test.go index 44903606234..54c7b6f65c7 100644 --- a/x/concentrated-liquidity/export_test.go +++ b/x/concentrated-liquidity/export_test.go @@ -60,7 +60,7 @@ func (k Keeper) SwapOutAmtGivenIn( tokenIn sdk.Coin, tokenOutDenom string, spreadFactor osmomath.Dec, - priceLimit osmomath.Dec) (calcTokenIn, calcTokenOut sdk.Coin, poolUpdates PoolUpdates, err error) { + priceLimit osmomath.BigDec) (calcTokenIn, calcTokenOut sdk.Coin, poolUpdates PoolUpdates, err error) { return k.swapOutAmtGivenIn(ctx, sender, pool, tokenIn, tokenOutDenom, spreadFactor, priceLimit) } @@ -70,7 +70,7 @@ func (k Keeper) ComputeOutAmtGivenIn( tokenInMin sdk.Coin, tokenOutDenom string, spreadFactor osmomath.Dec, - priceLimit osmomath.Dec, + priceLimit osmomath.BigDec, ) (swapResult SwapResult, poolUpdates PoolUpdates, err error) { return k.computeOutAmtGivenIn(ctx, poolId, tokenInMin, tokenOutDenom, spreadFactor, priceLimit) @@ -83,7 +83,7 @@ func (k Keeper) SwapInAmtGivenOut( desiredTokenOut sdk.Coin, tokenInDenom string, spreadFactor osmomath.Dec, - priceLimit osmomath.Dec) (calcTokenIn, calcTokenOut sdk.Coin, poolUpdates PoolUpdates, err error) { + priceLimit osmomath.BigDec) (calcTokenIn, calcTokenOut sdk.Coin, poolUpdates PoolUpdates, err error) { return k.swapInAmtGivenOut(ctx, sender, pool, desiredTokenOut, tokenInDenom, spreadFactor, priceLimit) } @@ -92,7 +92,7 @@ func (k Keeper) ComputeInAmtGivenOut( desiredTokenOut sdk.Coin, tokenInDenom string, spreadFactor osmomath.Dec, - priceLimit osmomath.Dec, + priceLimit osmomath.BigDec, poolId uint64, ) (swapResult SwapResult, poolUpdates PoolUpdates, err error) { @@ -321,7 +321,7 @@ func (k Keeper) GetLargestSupportedUptimeDuration(ctx sdk.Context) time.Duration func (k Keeper) SetupSwapStrategy(ctx sdk.Context, p types.ConcentratedPoolExtension, spreadFactor osmomath.Dec, tokenInDenom string, - priceLimit osmomath.Dec) (strategy swapstrategy.SwapStrategy, sqrtPriceLimit osmomath.BigDec, err error) { + priceLimit osmomath.BigDec) (strategy swapstrategy.SwapStrategy, sqrtPriceLimit osmomath.BigDec, err error) { return k.setupSwapStrategy(p, spreadFactor, tokenInDenom, priceLimit) } diff --git a/x/concentrated-liquidity/fuzz_test.go b/x/concentrated-liquidity/fuzz_test.go index 091c8e1dfa4..acd3d821bb4 100644 --- a/x/concentrated-liquidity/fuzz_test.go +++ b/x/concentrated-liquidity/fuzz_test.go @@ -288,7 +288,7 @@ func (s *KeeperTestSuite) swap(pool types.ConcentratedPoolExtension, swapInFunde // // Execute swap fmt.Printf("swap in: %s\n", swapInFunded) cacheCtx, writeOutGivenIn := s.Ctx.CacheContext() - _, tokenOut, _, err := s.clk.SwapOutAmtGivenIn(cacheCtx, s.TestAccs[0], pool, swapInFunded, swapOutDenom, pool.GetSpreadFactor(s.Ctx), osmomath.ZeroDec()) + _, tokenOut, _, err := s.clk.SwapOutAmtGivenIn(cacheCtx, s.TestAccs[0], pool, swapInFunded, swapOutDenom, pool.GetSpreadFactor(s.Ctx), osmomath.ZeroBigDec()) if errors.As(err, &types.InvalidAmountCalculatedError{}) { // If the swap we're about to execute will not generate enough output, we skip the swap. // it would error for a real user though. This is good though, since that user would just be burning funds. @@ -307,7 +307,7 @@ func (s *KeeperTestSuite) swap(pool types.ConcentratedPoolExtension, swapInFunde // We expect the returned amountIn to be roughly equal to the original swapInFunded. cacheCtx, _ = s.Ctx.CacheContext() fmt.Printf("swap out: %s\n", tokenOut) - amountInSwapResult, _, _, err := s.clk.SwapInAmtGivenOut(cacheCtx, s.TestAccs[0], pool, tokenOut, swapInFunded.Denom, pool.GetSpreadFactor(s.Ctx), osmomath.ZeroDec()) + amountInSwapResult, _, _, err := s.clk.SwapInAmtGivenOut(cacheCtx, s.TestAccs[0], pool, tokenOut, swapInFunded.Denom, pool.GetSpreadFactor(s.Ctx), osmomath.ZeroBigDec()) if errors.As(err, &types.InvalidAmountCalculatedError{}) { // If the swap we're about to execute will not generate enough output, we skip the swap. // it would error for a real user though. This is good though, since that user would just be burning funds. diff --git a/x/concentrated-liquidity/keeper_test.go b/x/concentrated-liquidity/keeper_test.go index 79f324bd67b..7e67b5253e8 100644 --- a/x/concentrated-liquidity/keeper_test.go +++ b/x/concentrated-liquidity/keeper_test.go @@ -546,7 +546,7 @@ func (s *KeeperTestSuite) swapToMinTickAndBack(spreadFactor osmomath.Dec, incent actualSwappedInZeroForOne, tokenOut, _, err := s.App.ConcentratedLiquidityKeeper.SwapOutAmtGivenIn( s.Ctx, swapper, pool, coinZeroIn, pool.GetToken1(), - spreadFactor, osmomath.ZeroDec(), + spreadFactor, osmomath.ZeroBigDec(), ) s.Require().NoError(err) @@ -562,7 +562,7 @@ func (s *KeeperTestSuite) swapToMinTickAndBack(spreadFactor osmomath.Dec, incent actualSwappedInOneForZero, inverseTokenOut, _, err := s.App.ConcentratedLiquidityKeeper.SwapOutAmtGivenIn( s.Ctx, swapper, pool, tokenOut, pool.GetToken0(), - spreadFactor, osmomath.ZeroDec(), + spreadFactor, osmomath.ZeroBigDec(), ) s.Require().NoError(err) diff --git a/x/concentrated-liquidity/position_test.go b/x/concentrated-liquidity/position_test.go index 6c46b7d08db..d50f0929c5d 100644 --- a/x/concentrated-liquidity/position_test.go +++ b/x/concentrated-liquidity/position_test.go @@ -1796,7 +1796,7 @@ func (s *KeeperTestSuite) TestTickRoundingEdgeCase() { swapAddr := testAccs[2] desiredTokenOut := sdk.NewCoin(USDC, osmomath.NewInt(10000)) s.FundAcc(swapAddr, sdk.NewCoins(sdk.NewCoin(ETH, osmomath.NewInt(1000000000000000000)))) - _, _, _, err := s.clk.SwapInAmtGivenOut(s.Ctx, swapAddr, pool, desiredTokenOut, ETH, osmomath.ZeroDec(), osmomath.ZeroDec()) + _, _, _, err := s.clk.SwapInAmtGivenOut(s.Ctx, swapAddr, pool, desiredTokenOut, ETH, osmomath.ZeroDec(), osmomath.ZeroBigDec()) s.Require().NoError(err) // Both positions should be able to withdraw successfully diff --git a/x/concentrated-liquidity/range_test.go b/x/concentrated-liquidity/range_test.go index 06be6e088e8..2b25dff1e58 100644 --- a/x/concentrated-liquidity/range_test.go +++ b/x/concentrated-liquidity/range_test.go @@ -357,7 +357,7 @@ func (s *KeeperTestSuite) executeRandomizedSwap(pool types.ConcentratedPoolExten } // Note that we set the price limit to zero to ensure that the swap can execute in either direction (gets automatically set to correct limit) - swappedIn, swappedOut, _, err := s.clk.SwapInAmtGivenOut(s.Ctx, swapAddress, pool, swapOutCoin, swapInDenom, pool.GetSpreadFactor(s.Ctx), osmomath.ZeroDec()) + swappedIn, swappedOut, _, err := s.clk.SwapInAmtGivenOut(s.Ctx, swapAddress, pool, swapOutCoin, swapInDenom, pool.GetSpreadFactor(s.Ctx), osmomath.ZeroBigDec()) s.Require().NoError(err) return swappedIn, swappedOut @@ -439,7 +439,7 @@ func (s *KeeperTestSuite) getInitialPositionAssets(pool types.ConcentratedPoolEx // Calculate asset amounts that would be required to get the required spot price (rounding up on asset1 to ensure we stay in the intended tick) asset0Amount := osmomath.NewInt(100000000000000) - asset1Amount := osmomath.NewDecFromInt(asset0Amount).Mul(requiredPrice.Dec()).Ceil().TruncateInt() + asset1Amount := osmomath.BigDecFromDec(osmomath.NewDecFromInt(asset0Amount)).Mul(requiredPrice).Ceil().Dec().TruncateInt() assetCoins := sdk.NewCoins( sdk.NewCoin(pool.GetToken0(), asset0Amount), diff --git a/x/concentrated-liquidity/spread_rewards_test.go b/x/concentrated-liquidity/spread_rewards_test.go index 64090079d6d..37e5bd86d92 100644 --- a/x/concentrated-liquidity/spread_rewards_test.go +++ b/x/concentrated-liquidity/spread_rewards_test.go @@ -1371,16 +1371,16 @@ func (s *KeeperTestSuite) TestFunctional_SpreadRewards_Swaps() { } // Swap multiple times USDC for ETH, therefore increasing the spot price - ticksActivatedAfterEachSwap, totalSpreadRewardsExpected, _, _ := s.swapAndTrackXTimesInARow(clPool.GetId(), DefaultCoin1, ETH, types.MaxSpotPrice, positions.numSwaps) + ticksActivatedAfterEachSwap, totalSpreadRewardsExpected, _, _ := s.swapAndTrackXTimesInARow(clPool.GetId(), DefaultCoin1, ETH, types.MaxSpotPriceBigDec, positions.numSwaps) 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.MinSpotPrice, positions.numSwaps) + ticksActivatedAfterEachSwap, totalSpreadRewardsExpected, _, _ = s.swapAndTrackXTimesInARow(clPool.GetId(), DefaultCoin0, USDC, types.MinSpotPriceBigDec, 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.MaxSpotPrice, positions.numSwaps) - ticksActivatedAfterEachSwapDown, totalSpreadRewardsExpectedDown, _, _ := s.swapAndTrackXTimesInARow(clPool.GetId(), DefaultCoin0, USDC, types.MinSpotPrice, positions.numSwaps) + ticksActivatedAfterEachSwapUp, totalSpreadRewardsExpectedUp, _, _ := s.swapAndTrackXTimesInARow(clPool.GetId(), DefaultCoin1, ETH, types.MaxSpotPriceBigDec, positions.numSwaps) + ticksActivatedAfterEachSwapDown, totalSpreadRewardsExpectedDown, _, _ := s.swapAndTrackXTimesInARow(clPool.GetId(), DefaultCoin0, USDC, types.MinSpotPriceBigDec, 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 @@ -1414,7 +1414,7 @@ func (s *KeeperTestSuite) TestFunctional_SpreadRewards_LP() { s.FundAcc(owner, fundCoins) // Errors since no position. - _, _, _, err := s.App.ConcentratedLiquidityKeeper.SwapOutAmtGivenIn(s.Ctx, owner, pool, sdk.NewCoin(ETH, osmomath.OneInt()), USDC, pool.GetSpreadFactor(s.Ctx), types.MaxSpotPrice) + _, _, _, err := s.App.ConcentratedLiquidityKeeper.SwapOutAmtGivenIn(s.Ctx, owner, pool, sdk.NewCoin(ETH, osmomath.OneInt()), USDC, pool.GetSpreadFactor(s.Ctx), types.MaxSpotPriceBigDec) s.Require().Error(err) // Create position in the default range 1. @@ -1422,7 +1422,7 @@ func (s *KeeperTestSuite) TestFunctional_SpreadRewards_LP() { s.Require().NoError(err) // Swap once. - ticksActivatedAfterEachSwap, totalSpreadRewardsExpected, _, _ := s.swapAndTrackXTimesInARow(pool.GetId(), DefaultCoin1, ETH, types.MaxSpotPrice, 1) + ticksActivatedAfterEachSwap, totalSpreadRewardsExpected, _, _ := s.swapAndTrackXTimesInARow(pool.GetId(), DefaultCoin1, ETH, types.MaxSpotPriceBigDec, 1) // Withdraw half. halfLiquidity := positionDataOne.Liquidity.Mul(osmomath.NewDecWithPrec(5, 1)) @@ -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.MinSpotPrice, 1) + ticksActivatedAfterEachSwap, totalSpreadRewardsExpected, _, _ = s.swapAndTrackXTimesInARow(pool.GetId(), DefaultCoin0, USDC, types.MinSpotPriceBigDec, 1) // This should claim under the hood for position 2 since full liquidity is removed. balanceBeforeWithdraw := s.App.BankKeeper.GetBalance(ctx, owner, ETH) @@ -1583,7 +1583,7 @@ func (s *KeeperTestSuite) tickStatusInvariance(ticksActivatedAfterEachSwap [][]i // swapAndTrackXTimesInARow performs `numSwaps` swaps and tracks the tick activated after each swap. // It also returns the total spread rewards collected, the total token in, and the total token out. -func (s *KeeperTestSuite) swapAndTrackXTimesInARow(poolId uint64, coinIn sdk.Coin, coinOutDenom string, priceLimit osmomath.Dec, numSwaps int) (ticksActivatedAfterEachSwap []int64, totalSpreadRewards sdk.Coins, totalTokenIn sdk.Coin, totalTokenOut sdk.Coin) { +func (s *KeeperTestSuite) swapAndTrackXTimesInARow(poolId uint64, coinIn sdk.Coin, coinOutDenom string, priceLimit osmomath.BigDec, numSwaps int) (ticksActivatedAfterEachSwap []int64, totalSpreadRewards sdk.Coins, totalTokenIn sdk.Coin, totalTokenOut sdk.Coin) { // Retrieve pool clPool, err := s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, poolId) s.Require().NoError(err) diff --git a/x/concentrated-liquidity/swaps.go b/x/concentrated-liquidity/swaps.go index 77edbb16651..8110400dc79 100644 --- a/x/concentrated-liquidity/swaps.go +++ b/x/concentrated-liquidity/swaps.go @@ -216,7 +216,7 @@ func (k Keeper) swapOutAmtGivenIn( tokenIn sdk.Coin, tokenOutDenom string, spreadFactor osmomath.Dec, - priceLimit osmomath.Dec, + priceLimit osmomath.BigDec, ) (calcTokenIn, calcTokenOut sdk.Coin, poolUpdates PoolUpdates, err error) { swapResult, poolUpdates, err := k.computeOutAmtGivenIn(ctx, pool.GetId(), tokenIn, tokenOutDenom, spreadFactor, priceLimit) if err != nil { @@ -247,7 +247,7 @@ func (k *Keeper) swapInAmtGivenOut( desiredTokenOut sdk.Coin, tokenInDenom string, spreadFactor osmomath.Dec, - priceLimit osmomath.Dec, + priceLimit osmomath.BigDec, ) (calcTokenIn, calcTokenOut sdk.Coin, poolUpdates PoolUpdates, err error) { swapResult, poolUpdates, err := k.computeInAmtGivenOut(ctx, desiredTokenOut, tokenInDenom, spreadFactor, priceLimit, pool.GetId()) if err != nil { @@ -278,7 +278,7 @@ func (k Keeper) CalcOutAmtGivenIn( spreadFactor osmomath.Dec, ) (tokenOut sdk.Coin, err error) { cacheCtx, _ := ctx.CacheContext() - swapResult, _, err := k.computeOutAmtGivenIn(cacheCtx, poolI.GetId(), tokenIn, tokenOutDenom, spreadFactor, osmomath.ZeroDec()) + swapResult, _, err := k.computeOutAmtGivenIn(cacheCtx, poolI.GetId(), tokenIn, tokenOutDenom, spreadFactor, osmomath.ZeroBigDec()) if err != nil { return sdk.Coin{}, err } @@ -293,7 +293,7 @@ func (k Keeper) CalcInAmtGivenOut( spreadFactor osmomath.Dec, ) (sdk.Coin, error) { cacheCtx, _ := ctx.CacheContext() - swapResult, _, err := k.computeInAmtGivenOut(cacheCtx, tokenOut, tokenInDenom, spreadFactor, osmomath.ZeroDec(), poolI.GetId()) + swapResult, _, err := k.computeInAmtGivenOut(cacheCtx, tokenOut, tokenInDenom, spreadFactor, osmomath.ZeroBigDec(), poolI.GetId()) if err != nil { return sdk.Coin{}, err } @@ -354,7 +354,7 @@ func (k Keeper) computeOutAmtGivenIn( tokenInMin sdk.Coin, tokenOutDenom string, spreadFactor osmomath.Dec, - priceLimit osmomath.Dec, + priceLimit osmomath.BigDec, ) (swapResult SwapResult, poolUpdates PoolUpdates, err error) { p, spreadRewardAccumulator, uptimeAccums, err := k.swapSetup(ctx, poolId, tokenInMin.Denom, tokenOutDenom) if err != nil { @@ -481,7 +481,7 @@ func (k Keeper) computeInAmtGivenOut( desiredTokenOut sdk.Coin, tokenInDenom string, spreadFactor osmomath.Dec, - priceLimit osmomath.Dec, + priceLimit osmomath.BigDec, poolId uint64, ) (swapResult SwapResult, poolUpdates PoolUpdates, err error) { p, spreadRewardAccumulator, uptimeAccums, err := k.swapSetup(ctx, poolId, tokenInDenom, desiredTokenOut.Denom) @@ -734,7 +734,7 @@ func checkDenomValidity(inDenom, outDenom, asset0, asset1 string) error { return nil } -func (k Keeper) setupSwapStrategy(p types.ConcentratedPoolExtension, spreadFactor osmomath.Dec, tokenInDenom string, priceLimit osmomath.Dec) (strategy swapstrategy.SwapStrategy, sqrtPriceLimit osmomath.BigDec, err error) { +func (k Keeper) setupSwapStrategy(p types.ConcentratedPoolExtension, spreadFactor osmomath.Dec, tokenInDenom string, priceLimit osmomath.BigDec) (strategy swapstrategy.SwapStrategy, sqrtPriceLimit osmomath.BigDec, err error) { zeroForOne := getZeroForOne(tokenInDenom, p.GetToken0()) // take provided price limit and turn this into a sqrt price limit since formulas use sqrtPrice @@ -838,7 +838,7 @@ func (k Keeper) ComputeMaxInAmtGivenMaxTicksCrossed( } // Setup the swap strategy - swapStrategy, _, err := k.setupSwapStrategy(p, p.GetSpreadFactor(cacheCtx), tokenInDenom, osmomath.ZeroDec()) + swapStrategy, _, err := k.setupSwapStrategy(p, p.GetSpreadFactor(cacheCtx), tokenInDenom, osmomath.ZeroBigDec()) if err != nil { return sdk.Coin{}, sdk.Coin{}, err } diff --git a/x/concentrated-liquidity/swaps_test.go b/x/concentrated-liquidity/swaps_test.go index 4752ee0ffa2..87f343d086e 100644 --- a/x/concentrated-liquidity/swaps_test.go +++ b/x/concentrated-liquidity/swaps_test.go @@ -33,7 +33,7 @@ type SwapTest struct { tokenInDenom string // Shared. - priceLimit osmomath.Dec + priceLimit osmomath.BigDec spreadFactor osmomath.Dec secondPositionLowerPrice osmomath.Dec secondPositionUpperPrice osmomath.Dec @@ -91,7 +91,7 @@ var ( "single position within one tick: usdc -> eth": { tokenIn: sdk.NewCoin("usdc", osmomath.NewInt(42000000)), tokenOutDenom: "eth", - priceLimit: osmomath.NewDec(5004), + priceLimit: osmomath.NewBigDec(5004), spreadFactor: osmomath.ZeroDec(), // from math import * // from decimal import * @@ -132,7 +132,7 @@ var ( "single position within one tick: usdc -> eth, with zero price limit": { tokenIn: sdk.NewCoin("usdc", osmomath.NewInt(42000000)), tokenOutDenom: "eth", - priceLimit: osmomath.ZeroDec(), + priceLimit: osmomath.ZeroBigDec(), spreadFactor: osmomath.ZeroDec(), // from math import * // from decimal import * @@ -173,7 +173,7 @@ var ( "single position within one tick: eth -> usdc": { tokenIn: sdk.NewCoin("eth", osmomath.NewInt(13370)), tokenOutDenom: "usdc", - priceLimit: osmomath.NewDec(4993), + priceLimit: osmomath.NewBigDec(4993), spreadFactor: osmomath.ZeroDec(), // from math import * // from decimal import * @@ -210,7 +210,7 @@ var ( "single position within one tick: eth -> usdc, with zero price limit": { tokenIn: sdk.NewCoin("eth", osmomath.NewInt(13370)), tokenOutDenom: "usdc", - priceLimit: osmomath.ZeroDec(), + priceLimit: osmomath.ZeroBigDec(), spreadFactor: osmomath.ZeroDec(), // from math import * // from decimal import * @@ -248,7 +248,7 @@ var ( "two positions within one tick: usdc -> eth": { tokenIn: sdk.NewCoin("usdc", osmomath.NewInt(42000000)), tokenOutDenom: "eth", - priceLimit: osmomath.NewDec(5002), + priceLimit: osmomath.NewBigDec(5002), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: DefaultLowerPrice, secondPositionUpperPrice: DefaultUpperPrice, @@ -284,7 +284,7 @@ var ( "two positions within one tick: eth -> usdc": { tokenIn: sdk.NewCoin("eth", osmomath.NewInt(13370)), tokenOutDenom: "usdc", - priceLimit: osmomath.NewDec(4996), + priceLimit: osmomath.NewBigDec(4996), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: DefaultLowerPrice, secondPositionUpperPrice: DefaultUpperPrice, @@ -328,7 +328,7 @@ var ( "two positions with consecutive price ranges: usdc -> eth": { tokenIn: sdk.NewCoin("usdc", osmomath.NewInt(10000000000)), tokenOutDenom: "eth", - priceLimit: osmomath.NewDec(6255), + priceLimit: osmomath.NewBigDec(6255), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: osmomath.NewDec(5500), secondPositionUpperPrice: osmomath.NewDec(6250), @@ -383,7 +383,7 @@ var ( "two positions with consecutive price ranges: eth -> usdc": { tokenIn: sdk.NewCoin("eth", osmomath.NewInt(2000000)), tokenOutDenom: "usdc", - priceLimit: osmomath.NewDec(3900), + priceLimit: osmomath.NewBigDec(3900), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: osmomath.NewDec(4000), secondPositionUpperPrice: osmomath.NewDec(4545), @@ -440,7 +440,7 @@ var ( "two positions with partially overlapping price ranges: usdc -> eth": { tokenIn: sdk.NewCoin("usdc", osmomath.NewInt(10000000000)), tokenOutDenom: "eth", - priceLimit: osmomath.NewDec(6056), + priceLimit: osmomath.NewBigDec(6056), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: osmomath.NewDec(5001), secondPositionUpperPrice: osmomath.NewDec(6250), @@ -494,7 +494,7 @@ var ( "two positions with partially overlapping price ranges, not utilizing full liquidity of second position: usdc -> eth": { tokenIn: sdk.NewCoin("usdc", osmomath.NewInt(8500000000)), tokenOutDenom: "eth", - priceLimit: osmomath.NewDec(6056), + priceLimit: osmomath.NewBigDec(6056), spreadFactor: osmomath.ZeroDec(), // from math import * // from decimal import * @@ -560,7 +560,7 @@ var ( "two positions with partially overlapping price ranges: eth -> usdc": { tokenIn: sdk.NewCoin("eth", osmomath.NewInt(2000000)), tokenOutDenom: "usdc", - priceLimit: osmomath.NewDec(4128), + priceLimit: osmomath.NewBigDec(4128), spreadFactor: osmomath.ZeroDec(), // from math import * // from decimal import * @@ -624,7 +624,7 @@ var ( "two positions with partially overlapping price ranges, not utilizing full liquidity of second position: eth -> usdc": { tokenIn: sdk.NewCoin("eth", osmomath.NewInt(1800000)), tokenOutDenom: "usdc", - priceLimit: osmomath.NewDec(4128), + priceLimit: osmomath.NewBigDec(4128), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: osmomath.NewDec(4000), secondPositionUpperPrice: osmomath.NewDec(4999), @@ -685,7 +685,7 @@ var ( "two sequential positions with a gap": { tokenIn: sdk.NewCoin("usdc", osmomath.NewInt(10000000000)), tokenOutDenom: "eth", - priceLimit: osmomath.NewDec(6106), + priceLimit: osmomath.NewBigDec(6106), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: osmomath.NewDec(5501), secondPositionUpperPrice: osmomath.NewDec(6250), @@ -741,7 +741,7 @@ var ( "single position within one tick, trade completes but slippage protection interrupts trade early: eth -> usdc": { tokenIn: sdk.NewCoin("eth", osmomath.NewInt(13370)), tokenOutDenom: "usdc", - priceLimit: osmomath.NewDec(4994), + priceLimit: osmomath.NewBigDec(4994), spreadFactor: osmomath.ZeroDec(), // from math import * // from decimal import * @@ -781,7 +781,7 @@ var ( // are estimated by utilizing scripts from scripts/cl/main.py tokenIn: sdk.NewCoin("usdc", osmomath.NewInt(42000000)), tokenOutDenom: "eth", - priceLimit: osmomath.NewDec(5004), + priceLimit: osmomath.NewBigDec(5004), spreadFactor: osmomath.MustNewDecFromStr("0.01"), expectedTokenIn: sdk.NewCoin("usdc", osmomath.NewInt(42000000)), expectedTokenOut: sdk.NewCoin("eth", osmomath.NewInt(8312)), @@ -797,7 +797,7 @@ var ( // are estimated by utilizing scripts from scripts/cl/main.py tokenIn: sdk.NewCoin("eth", osmomath.NewInt(13370)), tokenOutDenom: "usdc", - priceLimit: osmomath.NewDec(4990), + priceLimit: osmomath.NewBigDec(4990), spreadFactor: osmomath.MustNewDecFromStr("0.03"), secondPositionLowerPrice: DefaultLowerPrice, secondPositionUpperPrice: DefaultUpperPrice, @@ -818,7 +818,7 @@ var ( // are estimated by utilizing scripts from scripts/cl/main.py tokenIn: sdk.NewCoin("eth", osmomath.NewInt(2000000)), tokenOutDenom: "usdc", - priceLimit: osmomath.NewDec(4094), + priceLimit: osmomath.NewBigDec(4094), spreadFactor: osmomath.MustNewDecFromStr("0.05"), secondPositionLowerPrice: osmomath.NewDec(4000), secondPositionUpperPrice: osmomath.NewDec(4545), @@ -838,7 +838,7 @@ var ( // are estimated by utilizing scripts from scripts/cl/main.py tokenIn: sdk.NewCoin("usdc", osmomath.NewInt(10000000000)), tokenOutDenom: "eth", - priceLimit: osmomath.NewDec(6056), + priceLimit: osmomath.NewBigDec(6056), spreadFactor: osmomath.MustNewDecFromStr("0.1"), secondPositionLowerPrice: osmomath.NewDec(5001), secondPositionUpperPrice: osmomath.NewDec(6250), @@ -858,7 +858,7 @@ var ( // are estimated by utilizing scripts from scripts/cl/main.py tokenIn: sdk.NewCoin("eth", osmomath.NewInt(1800000)), tokenOutDenom: "usdc", - priceLimit: osmomath.NewDec(4128), + priceLimit: osmomath.NewBigDec(4128), spreadFactor: osmomath.MustNewDecFromStr("0.005"), secondPositionLowerPrice: osmomath.NewDec(4000), secondPositionUpperPrice: osmomath.NewDec(4999), @@ -878,7 +878,7 @@ var ( // are estimated by utilizing scripts from scripts/cl/main.py tokenIn: sdk.NewCoin("usdc", osmomath.NewInt(10000000000)), tokenOutDenom: "eth", - priceLimit: osmomath.NewDec(6106), + priceLimit: osmomath.NewBigDec(6106), secondPositionLowerPrice: osmomath.NewDec(5501), secondPositionUpperPrice: osmomath.NewDec(6250), spreadFactor: osmomath.MustNewDecFromStr("0.03"), @@ -897,7 +897,7 @@ var ( // are estimated by utilizing scripts from scripts/cl/main.py tokenIn: sdk.NewCoin("eth", osmomath.NewInt(13370)), tokenOutDenom: "usdc", - priceLimit: osmomath.NewDec(4994), + priceLimit: osmomath.NewBigDec(4994), spreadFactor: osmomath.MustNewDecFromStr("0.01"), expectedTokenIn: sdk.NewCoin("eth", osmomath.NewInt(13023)), expectedTokenOut: sdk.NewCoin("usdc", osmomath.NewInt(64417624)), @@ -914,14 +914,14 @@ var ( "single position within one tick, trade does not complete due to lack of liquidity: usdc -> eth": { tokenIn: sdk.NewCoin("usdc", osmomath.NewInt(5300000000)), tokenOutDenom: "eth", - priceLimit: osmomath.NewDec(6000), + priceLimit: osmomath.NewBigDec(6000), spreadFactor: osmomath.ZeroDec(), expectErr: true, }, "single position within one tick, trade does not complete due to lack of liquidity: eth -> usdc": { tokenIn: sdk.NewCoin("eth", osmomath.NewInt(1100000)), tokenOutDenom: "usdc", - priceLimit: osmomath.NewDec(4000), + priceLimit: osmomath.NewBigDec(4000), spreadFactor: osmomath.ZeroDec(), expectErr: true, }, @@ -936,7 +936,7 @@ var ( "single position within one tick: eth (in) -> usdc (out) | zfo": { tokenOut: sdk.NewCoin(USDC, osmomath.NewInt(42000000)), tokenInDenom: ETH, - priceLimit: osmomath.NewDec(4993), + priceLimit: osmomath.NewBigDec(4993), spreadFactor: osmomath.ZeroDec(), // from math import * // from decimal import * @@ -965,7 +965,7 @@ var ( "single position within one tick: usdc (in) -> eth (out) ofz": { tokenOut: sdk.NewCoin(ETH, osmomath.NewInt(13370)), tokenInDenom: USDC, - priceLimit: osmomath.NewDec(5010), + priceLimit: osmomath.NewBigDec(5010), spreadFactor: osmomath.ZeroDec(), // from math import * // from decimal import * @@ -994,7 +994,7 @@ var ( "two positions within one tick: eth (in) -> usdc (out) | zfo": { tokenOut: sdk.NewCoin("usdc", osmomath.NewInt(66829187)), tokenInDenom: "eth", - priceLimit: osmomath.NewDec(4990), + priceLimit: osmomath.NewBigDec(4990), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: DefaultLowerPrice, secondPositionUpperPrice: DefaultUpperPrice, @@ -1031,7 +1031,7 @@ var ( "two positions within one tick: usdc (in) -> eth (out) | ofz": { tokenOut: sdk.NewCoin("eth", osmomath.NewInt(8398)), tokenInDenom: "usdc", - priceLimit: osmomath.NewDec(5020), + priceLimit: osmomath.NewBigDec(5020), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: DefaultLowerPrice, secondPositionUpperPrice: DefaultUpperPrice, @@ -1065,7 +1065,7 @@ var ( "two positions with consecutive price ranges: eth (in) -> usdc (out) | zfo": { tokenOut: sdk.NewCoin("usdc", osmomath.NewInt(9103422788)), tokenInDenom: "eth", - priceLimit: osmomath.NewDec(3900), + priceLimit: osmomath.NewBigDec(3900), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: osmomath.NewDec(4000), secondPositionUpperPrice: osmomath.NewDec(4545), @@ -1126,7 +1126,7 @@ var ( "two positions with consecutive price ranges: usdc (in) -> eth (out) | ofz": { tokenOut: sdk.NewCoin(ETH, osmomath.NewInt(1820630)), tokenInDenom: USDC, - priceLimit: osmomath.NewDec(6106), + priceLimit: osmomath.NewBigDec(6106), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: osmomath.NewDec(5500), // 315000 secondPositionUpperPrice: osmomath.NewDec(6250), // 322500 @@ -1184,7 +1184,7 @@ var ( "two positions with partially overlapping price ranges: eth (in) -> usdc (out) | zfo": { tokenOut: sdk.NewCoin(USDC, osmomath.NewInt(9321276930)), tokenInDenom: ETH, - priceLimit: osmomath.NewDec(4128), + priceLimit: osmomath.NewBigDec(4128), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: osmomath.NewDec(4000), secondPositionUpperPrice: osmomath.NewDec(4999), @@ -1253,7 +1253,7 @@ var ( "two positions with partially overlapping price ranges, not utilizing full liquidity of second position: eth (in) -> usdc (out) | zfo": { tokenOut: sdk.NewCoin(USDC, osmomath.NewInt(8479320318)), tokenInDenom: ETH, - priceLimit: osmomath.NewDec(4128), + priceLimit: osmomath.NewBigDec(4128), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: osmomath.NewDec(4000), secondPositionUpperPrice: osmomath.NewDec(4999), @@ -1324,7 +1324,7 @@ var ( "two positions with partially overlapping price ranges: usdc (in) -> eth (out) | ofz": { tokenOut: sdk.NewCoin(ETH, osmomath.NewInt(1864161)), tokenInDenom: USDC, - priceLimit: osmomath.NewDec(6056), + priceLimit: osmomath.NewBigDec(6056), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: osmomath.NewDec(5001), secondPositionUpperPrice: osmomath.NewDec(6250), @@ -1385,7 +1385,7 @@ var ( "two positions with partially overlapping price ranges, not utilizing full liquidity of second position: usdc (in) -> eth (out) | ofz": { tokenOut: sdk.NewCoin(ETH, osmomath.NewInt(1609138)), tokenInDenom: USDC, - priceLimit: osmomath.NewDec(6056), + priceLimit: osmomath.NewBigDec(6056), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: osmomath.NewDec(5001), secondPositionUpperPrice: osmomath.NewDec(6250), @@ -1458,7 +1458,7 @@ var ( "two sequential positions with a gap usdc (in) -> eth (out) | ofz": { tokenOut: sdk.NewCoin(ETH, osmomath.NewInt(1820545)), tokenInDenom: USDC, - priceLimit: osmomath.NewDec(6106), + priceLimit: osmomath.NewBigDec(6106), spreadFactor: osmomath.ZeroDec(), secondPositionLowerPrice: osmomath.NewDec(5501), // 315010 secondPositionUpperPrice: osmomath.NewDec(6250), // 322500 @@ -1511,7 +1511,7 @@ var ( "single position within one tick, trade completes but slippage protection interrupts trade early: usdc (in) -> eth (out) | ofz": { tokenOut: sdk.NewCoin(ETH, osmomath.NewInt(1820545)), tokenInDenom: USDC, - priceLimit: osmomath.NewDec(5002), + priceLimit: osmomath.NewBigDec(5002), spreadFactor: osmomath.ZeroDec(), // from math import * // from decimal import * @@ -1539,7 +1539,7 @@ var ( "spread factor 1: single position within one tick: eth (in) -> usdc (out) (1% spread factor) | zfo": { tokenOut: sdk.NewCoin(USDC, osmomath.NewInt(42000000)), tokenInDenom: ETH, - priceLimit: osmomath.NewDec(4993), + priceLimit: osmomath.NewBigDec(4993), spreadFactor: osmomath.MustNewDecFromStr("0.01"), // from math import * // from decimal import * @@ -1576,7 +1576,7 @@ var ( "spread factor 2: two positions within one tick: usdc (in) -> eth (out) (3% spread factor) | ofz": { tokenOut: sdk.NewCoin(ETH, osmomath.NewInt(8398)), tokenInDenom: USDC, - priceLimit: osmomath.NewDec(5020), + priceLimit: osmomath.NewBigDec(5020), spreadFactor: osmomath.MustNewDecFromStr("0.03"), secondPositionLowerPrice: DefaultLowerPrice, secondPositionUpperPrice: DefaultUpperPrice, @@ -1619,7 +1619,7 @@ var ( "spread factor 3: two positions with consecutive price ranges: usdc (in) -> eth (out) (0.1% spread factor) | ofz": { tokenOut: sdk.NewCoin(ETH, osmomath.NewInt(1820630)), tokenInDenom: USDC, - priceLimit: osmomath.NewDec(6106), + priceLimit: osmomath.NewBigDec(6106), spreadFactor: osmomath.MustNewDecFromStr("0.001"), secondPositionLowerPrice: osmomath.NewDec(5500), // 315000 secondPositionUpperPrice: osmomath.NewDec(6250), // 322500 @@ -1678,7 +1678,7 @@ var ( "spread factor 4: two positions with partially overlapping price ranges: eth (in) -> usdc (out) (10% spread factor) | zfo": { tokenOut: sdk.NewCoin(USDC, osmomath.NewInt(9321276930)), tokenInDenom: ETH, - priceLimit: osmomath.NewDec(4128), + priceLimit: osmomath.NewBigDec(4128), spreadFactor: osmomath.MustNewDecFromStr("0.1"), secondPositionLowerPrice: osmomath.NewDec(4000), secondPositionUpperPrice: osmomath.NewDec(4999), @@ -1750,7 +1750,7 @@ var ( "spread factor 5: two positions with partially overlapping price ranges, not utilizing full liquidity of second position: usdc (in) -> eth (out) (5% spread factor) | ofz": { tokenOut: sdk.NewCoin(ETH, osmomath.NewInt(1609138)), tokenInDenom: USDC, - priceLimit: osmomath.NewDec(6056), + priceLimit: osmomath.NewBigDec(6056), spreadFactor: osmomath.MustNewDecFromStr("0.05"), secondPositionLowerPrice: osmomath.NewDec(5001), secondPositionUpperPrice: osmomath.NewDec(6250), @@ -1818,7 +1818,7 @@ var ( "spread factor 6: two sequential positions with a gap usdc (in) -> eth (out) (0.03% spread factor) | ofz": { tokenOut: sdk.NewCoin(ETH, osmomath.NewInt(1820545)), tokenInDenom: USDC, - priceLimit: osmomath.NewDec(6106), + priceLimit: osmomath.NewBigDec(6106), spreadFactor: osmomath.MustNewDecFromStr("0.0003"), secondPositionLowerPrice: osmomath.NewDec(5501), // 315010 secondPositionUpperPrice: osmomath.NewDec(6250), // 322500 @@ -1876,7 +1876,7 @@ var ( "spread factor 7: single position within one tick, trade completes but slippage protection interrupts trade early: usdc (in) -> eth (out) (1% spread factor) | ofz": { tokenOut: sdk.NewCoin(ETH, osmomath.NewInt(1820545)), tokenInDenom: USDC, - priceLimit: osmomath.NewDec(5002), + priceLimit: osmomath.NewBigDec(5002), spreadFactor: osmomath.MustNewDecFromStr("0.01"), // from math import * // from decimal import * @@ -1909,14 +1909,14 @@ var ( "single position within one tick, trade does not complete due to lack of liquidity: usdc -> eth ": { tokenOut: sdk.NewCoin("usdc", osmomath.NewInt(5300000000)), tokenInDenom: "eth", - priceLimit: osmomath.NewDec(6000), + priceLimit: osmomath.NewBigDec(6000), spreadFactor: osmomath.ZeroDec(), expectErr: true, }, "single position within one tick, trade does not complete due to lack of liquidity: eth -> usdc ": { tokenOut: sdk.NewCoin("eth", osmomath.NewInt(1100000)), tokenInDenom: "usdc", - priceLimit: osmomath.NewDec(4000), + priceLimit: osmomath.NewBigDec(4000), spreadFactor: osmomath.ZeroDec(), expectErr: true, }, @@ -2121,7 +2121,7 @@ func (s *KeeperTestSuite) TestSwap_NoPositions() { _, _, _, err := s.App.ConcentratedLiquidityKeeper.SwapInAmtGivenOut( s.Ctx, s.TestAccs[0], pool, DefaultCoin0, DefaultCoin1.Denom, - osmomath.ZeroDec(), osmomath.ZeroDec(), + osmomath.ZeroDec(), osmomath.ZeroBigDec(), ) s.Require().Error(err) s.Require().ErrorIs(err, types.NoSpotPriceWhenNoLiquidityError{PoolId: pool.GetId()}) @@ -2129,7 +2129,7 @@ func (s *KeeperTestSuite) TestSwap_NoPositions() { _, _, _, err = s.App.ConcentratedLiquidityKeeper.SwapOutAmtGivenIn( s.Ctx, s.TestAccs[0], pool, DefaultCoin0, DefaultCoin1.Denom, - osmomath.ZeroDec(), osmomath.ZeroDec(), + osmomath.ZeroDec(), osmomath.ZeroBigDec(), ) s.Require().Error(err) @@ -2843,7 +2843,7 @@ func (s *KeeperTestSuite) TestInverseRelationshipSwapOutAmtGivenIn() { secondTokenIn, secondTokenOut, _, err := s.App.ConcentratedLiquidityKeeper.SwapOutAmtGivenIn( s.Ctx, s.TestAccs[0], poolBefore, firstTokenOut, firstTokenIn.Denom, - DefaultZeroSpreadFactor, osmomath.ZeroDec(), + DefaultZeroSpreadFactor, osmomath.ZeroBigDec(), ) s.Require().NoError(err) @@ -2920,7 +2920,7 @@ func (s *KeeperTestSuite) TestInverseRelationshipSwapInAmtGivenOut() { secondTokenIn, secondTokenOut, _, err := s.App.ConcentratedLiquidityKeeper.SwapInAmtGivenOut( s.Ctx, s.TestAccs[0], poolBefore, firstTokenIn, firstTokenOut.Denom, - DefaultZeroSpreadFactor, osmomath.ZeroDec(), + DefaultZeroSpreadFactor, osmomath.ZeroBigDec(), ) s.Require().NoError(err) @@ -3186,7 +3186,7 @@ func (s *KeeperTestSuite) TestFunctionalSwaps() { // 5000 // Swap multiple times USDC for ETH, therefore increasing the spot price - _, _, totalTokenIn, totalTokenOut := s.swapAndTrackXTimesInARow(clPool.GetId(), swapCoin1, ETH, types.MaxSpotPrice, positions.numSwaps) + _, _, totalTokenIn, totalTokenOut := s.swapAndTrackXTimesInARow(clPool.GetId(), swapCoin1, ETH, types.MaxSpotPriceBigDec, positions.numSwaps) clPool, err := s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, clPool.GetId()) s.Require().NoError(err) @@ -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.MinSpotPrice, positions.numSwaps) + _, _, totalTokenIn, totalTokenOut = s.swapAndTrackXTimesInARow(clPool.GetId(), swapCoin0, USDC, types.MinSpotPriceBigDec, positions.numSwaps) clPool, err = s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, clPool.GetId()) s.Require().NoError(err) @@ -3295,7 +3295,7 @@ func (s *KeeperTestSuite) TestFunctionalSwaps() { } // Swap multiple times USDC for ETH, therefore increasing the spot price - _, _, totalTokenIn, totalTokenOut = s.swapAndTrackXTimesInARow(clPool.GetId(), swapCoin1, ETH, types.MaxSpotPrice, positions.numSwaps) + _, _, totalTokenIn, totalTokenOut = s.swapAndTrackXTimesInARow(clPool.GetId(), swapCoin1, ETH, types.MaxSpotPriceBigDec, 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.MinSpotPrice, positions.numSwaps) + _, _, totalTokenIn, totalTokenOut = s.swapAndTrackXTimesInARow(clPool.GetId(), swapCoin0, USDC, types.MinSpotPriceBigDec, positions.numSwaps) clPool, err = s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, clPool.GetId()) s.Require().NoError(err) @@ -3410,11 +3410,11 @@ func (s *KeeperTestSuite) TestInfiniteSwapLoop_OutGivenIn() { swapEthFunded := sdk.NewCoin(ETH, osmomath.Int(osmomath.MustNewDecFromStr("10000000000000000000000000000000000000000"))) swapUSDCFunded := sdk.NewCoin(USDC, osmomath.Int(osmomath.MustNewDecFromStr("10000"))) s.FundAcc(swapAddress, sdk.NewCoins(swapEthFunded, swapUSDCFunded)) - _, tokenOut, _, err := s.clk.SwapInAmtGivenOut(s.Ctx, swapAddress, pool, sdk.NewCoin(USDC, osmomath.NewInt(10000)), ETH, pool.GetSpreadFactor(s.Ctx), osmomath.ZeroDec()) + _, tokenOut, _, err := s.clk.SwapInAmtGivenOut(s.Ctx, swapAddress, pool, sdk.NewCoin(USDC, osmomath.NewInt(10000)), ETH, pool.GetSpreadFactor(s.Ctx), osmomath.ZeroBigDec()) s.Require().NoError(err) // Swap back in the amount that was swapped out to test the inverse relationship - _, _, _, err = s.clk.SwapOutAmtGivenIn(s.Ctx, swapAddress, pool, tokenOut, ETH, pool.GetSpreadFactor(s.Ctx), osmomath.ZeroDec()) + _, _, _, err = s.clk.SwapOutAmtGivenIn(s.Ctx, swapAddress, pool, tokenOut, ETH, pool.GetSpreadFactor(s.Ctx), osmomath.ZeroBigDec()) s.Require().NoError(err) } @@ -3535,10 +3535,10 @@ 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.MinInitializedTickV2, true, false) + amountOneOut, _, _ := s.computeSwapAmountsInGivenOut(poolId, pool.GetCurrentSqrtPrice(), types.MinInitializedTick, true, false) // estimate the amount in to fund - amountZeroIn, _, _ := s.computeSwapAmounts(poolId, pool.GetCurrentSqrtPrice(), types.MinInitializedTickV2, true, false) + amountZeroIn, _, _ := s.computeSwapAmounts(poolId, pool.GetCurrentSqrtPrice(), types.MinInitializedTick, true, false) // Fund swapper swapper := s.TestAccs[1] @@ -3549,7 +3549,7 @@ func (s *KeeperTestSuite) TestSwap_MinSpotPriceMigration() { tokenZeroIn, tokenOneOut, _, err := s.App.ConcentratedLiquidityKeeper.SwapInAmtGivenOut( s.Ctx, swapper, pool, coinOneOut, pool.GetToken0(), - osmomath.ZeroDec(), osmomath.ZeroDec(), + osmomath.ZeroDec(), osmomath.ZeroBigDec(), ) s.Require().NoError(err) @@ -3568,7 +3568,7 @@ func (s *KeeperTestSuite) TestSwap_MinSpotPriceMigration() { inverseTokenOut, _, _, err := s.App.ConcentratedLiquidityKeeper.SwapInAmtGivenOut( s.Ctx, swapper, pool, tokenZeroIn, pool.GetToken1(), - osmomath.ZeroDec(), osmomath.ZeroDec(), + osmomath.ZeroDec(), osmomath.ZeroBigDec(), ) s.Require().NoError(err) diff --git a/x/concentrated-liquidity/swaps_tick_cross_test.go b/x/concentrated-liquidity/swaps_tick_cross_test.go index 369b074b320..a6dfcc2733a 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.MinSqrtPrice) + zeroForOneSwapStrategy, _, err := s.App.ConcentratedLiquidityKeeper.SetupSwapStrategy(s.Ctx, pool, osmomath.ZeroDec(), pool.GetToken0(), types.MinSqrtPriceBigDec) s.Require().NoError(err) initializedTickValue := pool.GetCurrentTick() iter := zeroForOneSwapStrategy.InitializeNextTickIterator(s.Ctx, pool.GetId(), initializedTickValue) diff --git a/x/concentrated-liquidity/swapstrategy/swap_strategy.go b/x/concentrated-liquidity/swapstrategy/swap_strategy.go index fa2a02ec0f3..a176d37676a 100644 --- a/x/concentrated-liquidity/swapstrategy/swap_strategy.go +++ b/x/concentrated-liquidity/swapstrategy/swap_strategy.go @@ -104,25 +104,42 @@ func New(zeroForOne bool, sqrtPriceLimit osmomath.BigDec, storeKey sdk.StoreKey, // GetPriceLimit returns the price limit based on which token is being swapped in. // If zero in for one out, the price is decreasing. Therefore, min spot price is the limit. // If one in for zero out, the price is increasing. Therefore, max spot price is the limit. -func GetPriceLimit(zeroForOne bool) osmomath.Dec { +func GetPriceLimit(zeroForOne bool) osmomath.BigDec { if zeroForOne { - return types.MinSpotPrice + return types.MinSpotPriceBigDec } - return types.MaxSpotPrice + return types.MaxSpotPriceBigDec } -func GetSqrtPriceLimit(priceLimit osmomath.Dec, zeroForOne bool) (osmomath.BigDec, error) { +func GetSqrtPriceLimit(priceLimit osmomath.BigDec, zeroForOne bool) (osmomath.BigDec, error) { if priceLimit.IsZero() { if zeroForOne { - return osmomath.BigDecFromDec(types.MinSqrtPrice), nil + return types.MinSqrtPriceBigDec, nil } return osmomath.BigDecFromDec(types.MaxSqrtPrice), nil } - sqrtPriceLimit, err := osmomath.MonotonicSqrt(priceLimit) + // To keep state-compatibility with the original at-launch price range + // we utilize the same sqrt price function. + // Truncation should be fine since previous Osmosis version only supported + // 18 decimal price ranges. + if priceLimit.GTE(types.MinSqrtPriceBigDec) { + sqrtPriceLimit, err := osmomath.MonotonicSqrt(priceLimit.Dec()) + if err != nil { + return osmomath.BigDec{}, err + } + if err != nil { + return osmomath.BigDec{}, err + } + return osmomath.BigDecFromDec(sqrtPriceLimit), nil + } + + // On the newly extended lower price range, utilize the 36 decimal + // sqrt. + sqrtPriceLimit, err := osmomath.MonotonicSqrtBigDec(priceLimit) if err != nil { return osmomath.BigDec{}, err } - return osmomath.BigDecFromDec(sqrtPriceLimit), nil + return sqrtPriceLimit, nil } diff --git a/x/concentrated-liquidity/swapstrategy/zero_for_one.go b/x/concentrated-liquidity/swapstrategy/zero_for_one.go index 92f7358c9d5..8cd120e590d 100644 --- a/x/concentrated-liquidity/swapstrategy/zero_for_one.go +++ b/x/concentrated-liquidity/swapstrategy/zero_for_one.go @@ -252,7 +252,7 @@ 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(osmomath.BigDecFromDec(types.MinSqrtPrice)) { + if sqrtPrice.GT(currentSqrtPrice) || sqrtPrice.LT(types.MinSqrtPriceBigDec) { return types.SqrtPriceValidationError{SqrtPriceLimit: sqrtPrice, LowerBound: types.MinSqrtPriceBigDec, UpperBound: currentSqrtPrice} } return nil diff --git a/x/concentrated-liquidity/types/constants.go b/x/concentrated-liquidity/types/constants.go index 2b0d4aa0bd9..326803e79bf 100644 --- a/x/concentrated-liquidity/types/constants.go +++ b/x/concentrated-liquidity/types/constants.go @@ -41,6 +41,8 @@ var ( MinSqrtPrice = osmomath.MustMonotonicSqrt(MinSpotPrice) MaxSqrtPriceBigDec = osmomath.BigDecFromDec(MaxSqrtPrice) MinSqrtPriceBigDec = osmomath.BigDecFromDec(MinSqrtPrice) + MinSqrtPriceV2 = osmomath.MustMonotonicSqrtBigDec(MinSpotPriceV2) + // Supported uptimes preset to 1 ns, 1 min, 1 hr, 1D, 1W, 2W SupportedUptimes = []time.Duration{time.Nanosecond, time.Minute, time.Hour, time.Hour * 24, time.Hour * 24 * 7, time.Hour * 24 * 7 * 2} AuthorizedTickSpacing = []uint64{1, 10, 100, 1000} From a6b868f2f25a1764d08acff66ddaa876e5f9bb6f Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 11 Sep 2023 12:19:24 -0400 Subject: [PATCH 02/10] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56ab25d6d15..86e80ef8079 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#6256](https://github.com/osmosis-labs/osmosis/pull/6256) Refactor CalcPriceToTick to operate on BigDec price to support new price range. * [#6317](https://github.com/osmosis-labs/osmosis/pull/6317) Remove price return from CL `math.TickToSqrtPrice` +* [#6368](https://github.com/osmosis-labs/osmosis/pull/6368) Convert priceLimit API in CL swaps to BigDec ## v19.0.0 From 640bf20d57b952ba749361808b3d5093d8b68a02 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 11 Sep 2023 12:23:20 -0400 Subject: [PATCH 03/10] comment updates --- x/concentrated-liquidity/swapstrategy/swap_strategy.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/concentrated-liquidity/swapstrategy/swap_strategy.go b/x/concentrated-liquidity/swapstrategy/swap_strategy.go index a176d37676a..c6bcac0dc2c 100644 --- a/x/concentrated-liquidity/swapstrategy/swap_strategy.go +++ b/x/concentrated-liquidity/swapstrategy/swap_strategy.go @@ -121,9 +121,9 @@ func GetSqrtPriceLimit(priceLimit osmomath.BigDec, zeroForOne bool) (osmomath.Bi // To keep state-compatibility with the original at-launch price range // we utilize the same sqrt price function. - // Truncation should be fine since previous Osmosis version only supported - // 18 decimal price ranges. if priceLimit.GTE(types.MinSqrtPriceBigDec) { + // Truncation is fine since previous Osmosis version only supported + // 18 decimal price ranges. sqrtPriceLimit, err := osmomath.MonotonicSqrt(priceLimit.Dec()) if err != nil { return osmomath.BigDec{}, err From ae09d3ae9bc8ebf0e7df1443258b1a8b39ffc63f Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 11 Sep 2023 12:23:58 -0400 Subject: [PATCH 04/10] remove unused constant --- x/concentrated-liquidity/types/constants.go | 1 - 1 file changed, 1 deletion(-) diff --git a/x/concentrated-liquidity/types/constants.go b/x/concentrated-liquidity/types/constants.go index 326803e79bf..011cc57ab4e 100644 --- a/x/concentrated-liquidity/types/constants.go +++ b/x/concentrated-liquidity/types/constants.go @@ -41,7 +41,6 @@ var ( MinSqrtPrice = osmomath.MustMonotonicSqrt(MinSpotPrice) MaxSqrtPriceBigDec = osmomath.BigDecFromDec(MaxSqrtPrice) MinSqrtPriceBigDec = osmomath.BigDecFromDec(MinSqrtPrice) - MinSqrtPriceV2 = osmomath.MustMonotonicSqrtBigDec(MinSpotPriceV2) // Supported uptimes preset to 1 ns, 1 min, 1 hr, 1D, 1W, 2W SupportedUptimes = []time.Duration{time.Nanosecond, time.Minute, time.Hour, time.Hour * 24, time.Hour * 24 * 7, time.Hour * 24 * 7 * 2} From 8de14f00b462acfcaeeb3272ea05ab709016b66f Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 11 Sep 2023 12:46:53 -0400 Subject: [PATCH 05/10] fix test --- x/concentrated-liquidity/swapstrategy/swap_strategy_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x/concentrated-liquidity/swapstrategy/swap_strategy_test.go b/x/concentrated-liquidity/swapstrategy/swap_strategy_test.go index 8379a67643b..9368dd0655c 100644 --- a/x/concentrated-liquidity/swapstrategy/swap_strategy_test.go +++ b/x/concentrated-liquidity/swapstrategy/swap_strategy_test.go @@ -252,15 +252,15 @@ func (suite *StrategyTestSuite) TestComputeSwapState_Inverse() { func (suite *StrategyTestSuite) TestGetPriceLimit() { tests := map[string]struct { zeroForOne bool - expected osmomath.Dec + expected osmomath.BigDec }{ "zero for one -> min": { zeroForOne: true, - expected: types.MinSpotPrice, + expected: types.MinSpotPriceBigDec, }, "one for zero -> max": { zeroForOne: false, - expected: types.MaxSpotPrice, + expected: types.MaxSpotPriceBigDec, }, } From aef2e8162fd2d3ca5bab93bf82fdb1a942a1d9c7 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 11 Sep 2023 13:12:05 -0400 Subject: [PATCH 06/10] tests for GetSqrtPriceLimit --- .../swapstrategy/swap_strategy.go | 15 +++- .../swapstrategy/swap_strategy_test.go | 90 ++++++++++++++++++- 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/x/concentrated-liquidity/swapstrategy/swap_strategy.go b/x/concentrated-liquidity/swapstrategy/swap_strategy.go index c6bcac0dc2c..db4e6ba1ea6 100644 --- a/x/concentrated-liquidity/swapstrategy/swap_strategy.go +++ b/x/concentrated-liquidity/swapstrategy/swap_strategy.go @@ -111,6 +111,15 @@ func GetPriceLimit(zeroForOne bool) osmomath.BigDec { return types.MaxSpotPriceBigDec } +// GetSqrtPriceLimit returns sqrt price limit from price limit and swap strategy. +// If price limit is zero and strategy is zero for one, min sqrt price is returned. +// If price limit is zero and strategy is one for zero, max sqrt price is returned. +// If price limit is greater than MaxSpotPrice, an error is returned. +// Otherwise, if price limit is less that MinSpotPrice, a big decimal sqrt function +// is used to get the sqrt price limit. Otherwise, a decimal sqrt function is used. +// The sqrt function choice strategy applies to both zero for one and one for zero. +// Such a choice is made to keep state-compatibility with the original at-launch +// price range. func GetSqrtPriceLimit(priceLimit osmomath.BigDec, zeroForOne bool) (osmomath.BigDec, error) { if priceLimit.IsZero() { if zeroForOne { @@ -119,9 +128,13 @@ func GetSqrtPriceLimit(priceLimit osmomath.BigDec, zeroForOne bool) (osmomath.Bi return osmomath.BigDecFromDec(types.MaxSqrtPrice), nil } + if priceLimit.LT(types.MinSpotPriceV2) || priceLimit.GT(types.MaxSpotPriceBigDec) { + return osmomath.BigDec{}, types.PriceBoundError{ProvidedPrice: priceLimit, MinSpotPrice: types.MinSpotPriceV2, MaxSpotPrice: types.MaxSpotPrice} + } + // To keep state-compatibility with the original at-launch price range // we utilize the same sqrt price function. - if priceLimit.GTE(types.MinSqrtPriceBigDec) { + if priceLimit.GTE(types.MinSpotPriceBigDec) { // Truncation is fine since previous Osmosis version only supported // 18 decimal price ranges. sqrtPriceLimit, err := osmomath.MonotonicSqrt(priceLimit.Dec()) diff --git a/x/concentrated-liquidity/swapstrategy/swap_strategy_test.go b/x/concentrated-liquidity/swapstrategy/swap_strategy_test.go index 9368dd0655c..cc4b211b026 100644 --- a/x/concentrated-liquidity/swapstrategy/swap_strategy_test.go +++ b/x/concentrated-liquidity/swapstrategy/swap_strategy_test.go @@ -38,6 +38,7 @@ var ( three = osmomath.NewDec(3) four = osmomath.NewDec(4) five = osmomath.NewDec(5) + zeroBigDec = osmomath.ZeroBigDec() sqrt5000 = osmomath.MustNewDecFromStr("70.710678118654752440") // 5000 defaultSqrtPriceLower = osmomath.MustNewDecFromStr("70.688664163408836321") // approx 4996.89 defaultSqrtPriceUpper = sqrt5000 @@ -50,7 +51,7 @@ var ( defaultAmountReserves = osmomath.NewInt(1_000_000_000) DefaultCoins = sdk.NewCoins(sdk.NewCoin(ETH, defaultAmountReserves), sdk.NewCoin(USDC, defaultAmountReserves)) oneULPDec = osmomath.SmallestDec() - oneULPBigDec = osmomath.SmallestDec() + oneULPBigDec = osmomath.SmallestBigDec() ) func TestStrategyTestSuite(t *testing.T) { @@ -271,3 +272,90 @@ func (suite *StrategyTestSuite) TestGetPriceLimit() { }) } } + +// validates that the correct sqrt price limits are returned +// as dictated by the GetSqrtPriceLimit spec. +// Tests that the correct sqrt function is used depending +// on the value of the priceLimit input. Below min, the big dec sqrt is used. +// Above min, the dec sqrt is used. +func (suite *StrategyTestSuite) TestGetSqrtPriceLimit() { + var ( + oneULPUnderThreshold = types.MinSpotPriceBigDec.Sub(oneULPBigDec) + atThreshold = types.MinSpotPriceBigDec + oneULPAboveThreshold = types.MinSpotPriceBigDec.Add(oneULPBigDec) + ) + + tests := map[string]struct { + zeroForOne bool + priceLimit osmomath.BigDec + expected osmomath.BigDec + + expectedError error + }{ + "zero for one -> min": { + zeroForOne: true, + priceLimit: types.MinSpotPriceBigDec, + expected: types.MinSqrtPriceBigDec, + }, + "one for zero -> max": { + zeroForOne: false, + priceLimit: types.MaxSpotPriceBigDec, + expected: types.MaxSqrtPriceBigDec, + }, + "zero and zero for one -> min": { + zeroForOne: true, + priceLimit: zeroBigDec, + expected: types.MinSqrtPriceBigDec, + }, + "zero and one for zero -> max": { + zeroForOne: false, + priceLimit: zeroBigDec, + expected: types.MaxSqrtPriceBigDec, + }, + "higher than max and zero for one -> overflow error": { + zeroForOne: false, + priceLimit: types.MaxSpotPriceBigDec.Add(oneULPBigDec), + + expectedError: types.PriceBoundError{ProvidedPrice: types.MaxSpotPriceBigDec.Add(oneULPBigDec), MinSpotPrice: types.MinSpotPriceV2, MaxSpotPrice: types.MaxSpotPrice}, + }, + "higher than max and one for zero -> overflow error": { + zeroForOne: true, + priceLimit: types.MaxSpotPriceBigDec.Add(oneULPBigDec), + + expectedError: types.PriceBoundError{ProvidedPrice: types.MaxSpotPriceBigDec.Add(oneULPBigDec), MinSpotPrice: types.MinSpotPriceV2, MaxSpotPrice: types.MaxSpotPrice}, + }, + "under 10^-12, big dec sqrt is used": { + zeroForOne: true, + priceLimit: oneULPUnderThreshold, + + expected: osmomath.MustMonotonicSqrtBigDec(oneULPUnderThreshold), + }, + "at 10^-12 price threshold, dec sqrt is used": { + zeroForOne: true, + priceLimit: atThreshold, + + expected: osmomath.BigDecFromDec(osmomath.MustMonotonicSqrt(atThreshold.Dec())), + }, + "above 10^-12, dec sqrt is used": { + zeroForOne: true, + priceLimit: oneULPAboveThreshold, + + expected: osmomath.BigDecFromDec(osmomath.MustMonotonicSqrt(oneULPAboveThreshold.Dec())), + }, + } + + for name, tc := range tests { + suite.Run(name, func() { + priceLimit, err := swapstrategy.GetSqrtPriceLimit(tc.priceLimit, tc.zeroForOne) + + if tc.expectedError != nil { + suite.Require().Error(err) + suite.Require().Equal(tc.expectedError.Error(), err.Error()) + return + } + + suite.Require().NoError(err) + suite.Require().Equal(tc.expected, priceLimit) + }) + } +} From da738a2b3d507c02fc702343a32d80b524ba211b Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 12 Sep 2023 13:22:37 -0400 Subject: [PATCH 07/10] refactor(CL): rounding edge case in zfo strategy and tests --- .../swapstrategy/zero_for_one.go | 19 +++++++++ .../swapstrategy/zero_for_one_test.go | 42 +++++++++++++++---- x/concentrated-liquidity/types/constants.go | 1 + 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/x/concentrated-liquidity/swapstrategy/zero_for_one.go b/x/concentrated-liquidity/swapstrategy/zero_for_one.go index 8cd120e590d..fe47dfe4904 100644 --- a/x/concentrated-liquidity/swapstrategy/zero_for_one.go +++ b/x/concentrated-liquidity/swapstrategy/zero_for_one.go @@ -26,6 +26,10 @@ type zeroForOneStrategy struct { var _ SwapStrategy = (*zeroForOneStrategy)(nil) +var ( + smallestBigDec = osmomath.SmallestBigDec() +) + func (s zeroForOneStrategy) ZeroForOne() bool { return true } // GetSqrtTargetPrice returns the target square root price given the next tick square root price. @@ -77,6 +81,21 @@ func (s zeroForOneStrategy) ComputeSwapWithinBucketOutGivenIn(sqrtPriceCurrent, sqrtPriceNext = math.GetNextSqrtPriceFromAmount0InRoundingUp(sqrtPriceCurrent, liquidityBigDec, amountZeroInRemainingLessSpreadReward) } + // This edge case deals with an edge case stemming from rounding. + // If the computed sqrtPriceNext is 1 BigDec ULP away from the sqrtPriceTarget AND + // amount zero in needed to reach target is 1 unit greater than the amount zero remaining after spread rewards, + // we deems this as a rounding error and make sqrtPriceNext equal to sqrtPriceTarget + // while consuming all amoung in actually remaining. + // + // Without this change, we reach an infinite loop swap condition where we try to swap until the target but fail to consume + // any amount in to advance the swap by only one ULP. + // + // Changing the rounding behavior causes issues in other parts of the system where we end up overconsuming the final amount in. + if sqrtPriceNext.Sub(sqrtPriceTarget).Equal(smallestBigDec) && amountZeroIn.Sub(amountZeroInRemainingLessSpreadReward).Equal(oneBigDec) { + sqrtPriceNext = sqrtPriceTarget + amountZeroIn = amountZeroInRemainingLessSpreadReward + } + hasReachedTarget := sqrtPriceTarget.Equal(sqrtPriceNext) // If the sqrt price target was not reached, recalculate how much of the amount remaining after spread reward was needed diff --git a/x/concentrated-liquidity/swapstrategy/zero_for_one_test.go b/x/concentrated-liquidity/swapstrategy/zero_for_one_test.go index 2dec6cc12ac..13328cf2eaa 100644 --- a/x/concentrated-liquidity/swapstrategy/zero_for_one_test.go +++ b/x/concentrated-liquidity/swapstrategy/zero_for_one_test.go @@ -61,7 +61,7 @@ func (suite *StrategyTestSuite) TestComputeSwapStepOutGivenIn_ZeroForOne() { // sqrtPriceCurrent, sqrtPriceTarget, liquidity are all set to defaults defined above. tests := map[string]struct { sqrtPriceCurrent osmomath.BigDec - sqrtPriceTarget osmomath.Dec + sqrtPriceTarget osmomath.BigDec liquidity osmomath.Dec amountZeroInRemaining osmomath.Dec @@ -74,7 +74,7 @@ func (suite *StrategyTestSuite) TestComputeSwapStepOutGivenIn_ZeroForOne() { }{ "1: no spread reward - reach target": { sqrtPriceCurrent: sqrtPriceCurrent, - sqrtPriceTarget: sqrtPriceTarget, + sqrtPriceTarget: osmomath.BigDecFromDec(sqrtPriceTarget), liquidity: defaultLiquidity, // add 100 more @@ -90,7 +90,7 @@ func (suite *StrategyTestSuite) TestComputeSwapStepOutGivenIn_ZeroForOne() { }, "2: no spread reward - do not reach target": { sqrtPriceCurrent: sqrtPriceCurrent, - sqrtPriceTarget: sqrtPriceTarget, + sqrtPriceTarget: osmomath.BigDecFromDec(sqrtPriceTarget), liquidity: defaultLiquidity, amountZeroInRemaining: defaultAmountZero.Sub(osmomath.NewDec(100)), @@ -104,7 +104,7 @@ func (suite *StrategyTestSuite) TestComputeSwapStepOutGivenIn_ZeroForOne() { }, "3: 3% spread reward - reach target": { sqrtPriceCurrent: sqrtPriceCurrent, - sqrtPriceTarget: sqrtPriceTarget, + sqrtPriceTarget: osmomath.BigDecFromDec(sqrtPriceTarget), liquidity: defaultLiquidity, // add 100 more @@ -120,7 +120,7 @@ func (suite *StrategyTestSuite) TestComputeSwapStepOutGivenIn_ZeroForOne() { }, "4: 3% spread reward - do not reach target": { sqrtPriceCurrent: sqrtPriceCurrent, - sqrtPriceTarget: sqrtPriceTarget, + sqrtPriceTarget: osmomath.BigDecFromDec(sqrtPriceTarget), liquidity: defaultLiquidity, amountZeroInRemaining: defaultAmountZero.Sub(osmomath.NewDec(100)).Quo(one.Sub(defaultSpreadReward)), @@ -138,7 +138,7 @@ func (suite *StrategyTestSuite) TestComputeSwapStepOutGivenIn_ZeroForOne() { "5: sub osmomath.Dec ULP precision movement. Supported by osmomath.BigDec ULP": { // Note the numbers are hand-picked to reproduce this specific case. sqrtPriceCurrent: osmomath.MustNewBigDecFromStr("0.000001000049998751"), - sqrtPriceTarget: osmomath.MustNewDecFromStr("0.000001000049998750"), + sqrtPriceTarget: osmomath.MustNewBigDecFromStr("0.000001000049998750"), liquidity: osmomath.MustNewDecFromStr("100002498062401598791.937822606808718081"), amountZeroInRemaining: osmomath.NewDec(99), @@ -159,7 +159,7 @@ func (suite *StrategyTestSuite) TestComputeSwapStepOutGivenIn_ZeroForOne() { "6: sub osmomath.BigDec ULP precision movement. Nothing charged for amountIn due to precision loss. No amounts consumed": { // Note the numbers are hand-picked to reproduce this specific case. sqrtPriceCurrent: osmomath.MustNewBigDecFromStr("0.000001000049998751"), - sqrtPriceTarget: osmomath.MustNewDecFromStr("0.000001000049998750"), + sqrtPriceTarget: osmomath.MustNewBigDecFromStr("0.000001000049998750"), liquidity: osmomath.MustNewDecFromStr("100002498062401598791.937822606808718081"), amountZeroInRemaining: osmomath.SmallestDec(), @@ -176,7 +176,7 @@ func (suite *StrategyTestSuite) TestComputeSwapStepOutGivenIn_ZeroForOne() { "7: precisely osmomath.BigDec ULP precision movement. Amounts in and out are consumed.": { // Note the numbers are hand-picked to reproduce this specific case. sqrtPriceCurrent: osmomath.MustNewBigDecFromStr("0.000001000049998751"), - sqrtPriceTarget: osmomath.MustNewDecFromStr("0.000001000049998750"), + sqrtPriceTarget: osmomath.MustNewBigDecFromStr("0.000001000049998750"), liquidity: osmomath.MustNewDecFromStr("100002498062401598791.937822606808718081"), amountZeroInRemaining: osmomath.SmallestDec().MulInt64(100000000000000), @@ -195,12 +195,36 @@ func (suite *StrategyTestSuite) TestComputeSwapStepOutGivenIn_ZeroForOne() { expectedAmountOneOut: osmomath.SmallestDec().MulInt64(100), expectedSpreadRewardChargeTotal: osmomath.ZeroDec(), }, + "8 one ULP off from reaching target and one token zero in away (from fuzz seed 1694529692)": { + sqrtPriceCurrent: types.MinSqrtPriceBigDec, + sqrtPriceTarget: types.MinSqrtPriceV2, + liquidity: osmomath.MustNewDecFromStr("480519227415939839.324992153049511386"), + amountZeroInRemaining: osmomath.MustNewDecFromStr("480519226935420611909052313724519.000000000000000000"), + spreadFactor: osmomath.ZeroDec(), + + expectedSqrtPriceNext: types.MinSqrtPriceV2, + amountZeroInConsumed: osmomath.MustNewDecFromStr("480519226935420611909052313724519.000000000000000000"), + expectedAmountOneOut: osmomath.MustNewDecFromStr("480519226935.420611909052313724"), + expectedSpreadRewardChargeTotal: osmomath.ZeroDec(), + }, + "9 one ULP off from reaching target and one token zero in away (from fuzz seed 1694529692)": { + sqrtPriceCurrent: types.MinSqrtPriceV2.Add(osmomath.SmallestBigDec()), + sqrtPriceTarget: types.MinSqrtPriceV2, + liquidity: osmomath.MustNewDecFromStr("480519227415939839.324992153049511386"), + amountZeroInRemaining: osmomath.MustNewDecFromStr("480519227415.000000000000000000"), + spreadFactor: osmomath.ZeroDec(), + + expectedSqrtPriceNext: types.MinSqrtPriceV2, + amountZeroInConsumed: osmomath.MustNewDecFromStr("480519227415.000000000000000000"), + expectedAmountOneOut: osmomath.ZeroDec(), + expectedSpreadRewardChargeTotal: osmomath.ZeroDec(), + }, } for name, tc := range tests { suite.Run(name, func() { strategy := suite.setupNewZeroForOneSwapStrategy(types.MaxSqrtPrice, tc.spreadFactor) - sqrtPriceNext, amountZeroIn, amountOneOut, spreadRewardChargeTotal := strategy.ComputeSwapWithinBucketOutGivenIn(tc.sqrtPriceCurrent, osmomath.BigDecFromDec(tc.sqrtPriceTarget), tc.liquidity, tc.amountZeroInRemaining) + sqrtPriceNext, amountZeroIn, amountOneOut, spreadRewardChargeTotal := strategy.ComputeSwapWithinBucketOutGivenIn(tc.sqrtPriceCurrent, tc.sqrtPriceTarget, tc.liquidity, tc.amountZeroInRemaining) suite.Require().Equal(tc.expectedSqrtPriceNext, sqrtPriceNext) suite.Require().Equal(tc.expectedAmountOneOut, amountOneOut) diff --git a/x/concentrated-liquidity/types/constants.go b/x/concentrated-liquidity/types/constants.go index 011cc57ab4e..326803e79bf 100644 --- a/x/concentrated-liquidity/types/constants.go +++ b/x/concentrated-liquidity/types/constants.go @@ -41,6 +41,7 @@ var ( MinSqrtPrice = osmomath.MustMonotonicSqrt(MinSpotPrice) MaxSqrtPriceBigDec = osmomath.BigDecFromDec(MaxSqrtPrice) MinSqrtPriceBigDec = osmomath.BigDecFromDec(MinSqrtPrice) + MinSqrtPriceV2 = osmomath.MustMonotonicSqrtBigDec(MinSpotPriceV2) // Supported uptimes preset to 1 ns, 1 min, 1 hr, 1D, 1W, 2W SupportedUptimes = []time.Duration{time.Nanosecond, time.Minute, time.Hour, time.Hour * 24, time.Hour * 24 * 7, time.Hour * 24 * 7 * 2} From a4c03a43a50c95c033847e893b67edc6badbce9b Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 12 Sep 2023 13:31:02 -0400 Subject: [PATCH 08/10] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86e80ef8079..cc50c51b533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#6334](https://github.com/osmosis-labs/osmosis/pull/6334) fix: enable taker fee cli * [#6352](https://github.com/osmosis-labs/osmosis/pull/6352) Reduce error blow-up in CalcAmount0Delta by changing the order of math operations. +* [#6379](https://github.com/osmosis-labs/osmosis/pull/6379) Fix rounding edge case in zfo strategy that leads to +infinite loop in swap out given in. ### API Breaks From 76596555e35c667990a99e6d81d3775b3265ee32 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 12 Sep 2023 13:37:45 -0400 Subject: [PATCH 09/10] lint --- x/concentrated-liquidity/swapstrategy/zero_for_one.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/concentrated-liquidity/swapstrategy/zero_for_one.go b/x/concentrated-liquidity/swapstrategy/zero_for_one.go index fe47dfe4904..c7f2106e789 100644 --- a/x/concentrated-liquidity/swapstrategy/zero_for_one.go +++ b/x/concentrated-liquidity/swapstrategy/zero_for_one.go @@ -85,7 +85,7 @@ func (s zeroForOneStrategy) ComputeSwapWithinBucketOutGivenIn(sqrtPriceCurrent, // If the computed sqrtPriceNext is 1 BigDec ULP away from the sqrtPriceTarget AND // amount zero in needed to reach target is 1 unit greater than the amount zero remaining after spread rewards, // we deems this as a rounding error and make sqrtPriceNext equal to sqrtPriceTarget - // while consuming all amoung in actually remaining. + // while consuming all among in actually remaining. // // Without this change, we reach an infinite loop swap condition where we try to swap until the target but fail to consume // any amount in to advance the swap by only one ULP. From fe284da7d1e9cb0c6198ea81e0dc17f2cf9d0143 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 12 Sep 2023 13:53:21 -0400 Subject: [PATCH 10/10] updates --- x/concentrated-liquidity/swapstrategy/zero_for_one.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x/concentrated-liquidity/swapstrategy/zero_for_one.go b/x/concentrated-liquidity/swapstrategy/zero_for_one.go index c7f2106e789..31fab6ce0db 100644 --- a/x/concentrated-liquidity/swapstrategy/zero_for_one.go +++ b/x/concentrated-liquidity/swapstrategy/zero_for_one.go @@ -81,14 +81,14 @@ func (s zeroForOneStrategy) ComputeSwapWithinBucketOutGivenIn(sqrtPriceCurrent, sqrtPriceNext = math.GetNextSqrtPriceFromAmount0InRoundingUp(sqrtPriceCurrent, liquidityBigDec, amountZeroInRemainingLessSpreadReward) } - // This edge case deals with an edge case stemming from rounding. + // The code snippet below deals with an edge case stemming from rounding. // If the computed sqrtPriceNext is 1 BigDec ULP away from the sqrtPriceTarget AND // amount zero in needed to reach target is 1 unit greater than the amount zero remaining after spread rewards, - // we deems this as a rounding error and make sqrtPriceNext equal to sqrtPriceTarget - // while consuming all among in actually remaining. + // we deem this as a rounding error and make sqrtPriceNext equal to sqrtPriceTarget + // while consuming all amount in actually remaining. // // Without this change, we reach an infinite loop swap condition where we try to swap until the target but fail to consume - // any amount in to advance the swap by only one ULP. + // any amount in to advance by only 1 ULP. // // Changing the rounding behavior causes issues in other parts of the system where we end up overconsuming the final amount in. if sqrtPriceNext.Sub(sqrtPriceTarget).Equal(smallestBigDec) && amountZeroIn.Sub(amountZeroInRemainingLessSpreadReward).Equal(oneBigDec) {