From 615994cda7a7b69d7e6348e49b4e77ac781ba06d Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Sat, 13 Apr 2024 01:31:17 +0900 Subject: [PATCH] Speedup quo round up, start CL speedup integration (#8014) * Speedup quo round up * Code reuse * Missed a code re-use point * Add future notes * Comment cleanup * Auto: update go.mod after push to dev/speedup_quoroundup that modified dependencies locally * Add new fn: NewBigDecFromDecMulDec * Auto: update go.mod after push to dev/speedup_quoroundup that modified dependencies locally * Remove some extra ops from CL * Further perf notes * Make faster QuoRoundUpNextIntMut * Auto: update go.mod after push to dev/speedup_quoroundup that modified dependencies locally * Remove another 2 BigDec ops * Add another dec op * Auto: update go.mod after push to dev/speedup_quoroundup that modified dependencies locally * Start moving some liquidity calls to Dec not BigDec * One more BigDec x Dec op * Auto: update go.mod after push to dev/speedup_quoroundup that modified dependencies locally * Another liq BigDec -> Dec * Missed one step * Move another Liquidity BigDec -> Dec * Minor spread reward update * Make CalcAmount1Dec use Dec for Liquidity * Make one more op mutative * One more speedup * Fix test * Speedup SpotPrice impl --------- Co-authored-by: github-actions --- CHANGELOG.md | 2 +- osmomath/decimal.go | 155 +++++++++--------- osmomath/decimal_test.go | 74 +++++++++ x/concentrated-liquidity/incentives_test.go | 4 +- x/concentrated-liquidity/math/math.go | 35 ++-- x/concentrated-liquidity/math/math_test.go | 126 +++++++++----- x/concentrated-liquidity/model/pool.go | 15 +- x/concentrated-liquidity/model/pool_test.go | 19 +-- x/concentrated-liquidity/position_test.go | 4 +- .../swaps_tick_cross_test.go | 36 ++-- .../swapstrategy/one_for_zero.go | 24 ++- .../swapstrategy/spread_rewards.go | 12 +- .../swapstrategy/swap_strategy.go | 2 +- .../swapstrategy/zero_for_one.go | 19 +-- 14 files changed, 321 insertions(+), 206 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8ff0eb8aef..a6911b45971 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### State Compatible -* [#8006](https://github.com/osmosis-labs/osmosis/pull/8006) Speedup many BigDec operations +* [#8006](https://github.com/osmosis-labs/osmosis/pull/8006), [#8014](https://github.com/osmosis-labs/osmosis/pull/8014) Speedup many BigDec operations ## v24.0.1 diff --git a/osmomath/decimal.go b/osmomath/decimal.go index cb6eb82bcdd..3027a12fc8d 100644 --- a/osmomath/decimal.go +++ b/osmomath/decimal.go @@ -44,12 +44,14 @@ var ( squaredPrecisionReuse = new(big.Int).Mul(defaultBigDecPrecisionReuse, defaultBigDecPrecisionReuse) bigDecDecPrecisionFactorDiff = new(big.Int).Exp(big.NewInt(10), big.NewInt(BigDecPrecision-DecPrecision), nil) + tenTimesPrecision = new(big.Int).Exp(big.NewInt(10), big.NewInt(BigDecPrecision+1), nil) fivePrecision = new(big.Int).Quo(defaultBigDecPrecisionReuse, big.NewInt(2)) fivePrecisionSDKDec = new(big.Int).Quo(precisionReuseSDKDec, big.NewInt(2)) precisionMultipliers []*big.Int zeroInt = big.NewInt(0) oneInt = big.NewInt(1) + fiveInt = big.NewInt(5) tenInt = big.NewInt(10) // log_2(e) @@ -172,6 +174,11 @@ func NewBigDecFromIntWithPrec(i BigInt, prec int64) BigDec { } } +func NewBigDecFromDecMulDec(a, b Dec) BigDec { + newBi := new(big.Int).Mul(a.BigIntMut(), b.BigIntMut()) + return BigDec{newBi} +} + // create a decimal from an input decimal string. // valid must come in the form: // @@ -294,9 +301,7 @@ func (d BigDec) Add(d2 BigDec) BigDec { func (d BigDec) AddMut(d2 BigDec) BigDec { d.i.Add(d.i, d2.i) - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } + assertMaxBitLen(d.i) return d } @@ -310,10 +315,7 @@ func (d BigDec) Sub(d2 BigDec) BigDec { func (d BigDec) SubMut(d2 BigDec) BigDec { res := d.i.Sub(d.i, d2.i) - - if res.BitLen() > maxDecBitLen { - panic("Int overflow") - } + assertMaxBitLen(res) return BigDec{res} } @@ -344,9 +346,7 @@ func (d BigDec) MulMut(d2 BigDec) BigDec { d.i.Mul(d.i, d2.i) d.i = chopPrecisionAndRound(d.i) - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } + assertMaxBitLen(d.i) return BigDec{d.i} } @@ -360,9 +360,7 @@ func (d BigDec) MulDecMut(d2 Dec) BigDec { d.i.Mul(d.i, d2.BigIntMut()) d.i = chopPrecisionAndRoundSdkDec(d.i) - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } + assertMaxBitLen(d.i) return BigDec{d.i} } @@ -370,20 +368,14 @@ func (d BigDec) MulDecMut(d2 Dec) BigDec { func (d BigDec) MulTruncate(d2 BigDec) BigDec { mul := new(big.Int).Mul(d.i, d2.i) chopped := chopPrecisionAndTruncateMut(mul, defaultBigDecPrecisionReuse) - - if chopped.BitLen() > maxDecBitLen { - panic("Int overflow") - } + assertMaxBitLen(chopped) return BigDec{chopped} } func (d BigDec) MulTruncateDec(d2 Dec) BigDec { mul := new(big.Int).Mul(d.i, d2.BigIntMut()) chopped := chopPrecisionAndTruncateMut(mul, precisionReuseSDKDec) - - if chopped.BitLen() > maxDecBitLen { - panic("Int overflow") - } + assertMaxBitLen(chopped) return BigDec{chopped} } @@ -391,20 +383,22 @@ func (d BigDec) MulTruncateDec(d2 Dec) BigDec { func (d BigDec) MulRoundUp(d2 BigDec) BigDec { mul := new(big.Int).Mul(d.i, d2.i) chopped := chopPrecisionAndRoundUpMut(mul, defaultBigDecPrecisionReuse) + assertMaxBitLen(chopped) + return BigDec{chopped} +} - if chopped.BitLen() > maxDecBitLen { - panic("Int overflow") - } +// multiplication round up by Dec +func (d BigDec) MulRoundUpDec(d2 Dec) BigDec { + mul := new(big.Int).Mul(d.i, d2.BigIntMut()) + chopped := chopPrecisionAndRoundUpMut(mul, precisionReuseSDKDec) + assertMaxBitLen(chopped) return BigDec{chopped} } // multiplication func (d BigDec) MulInt(i BigInt) BigDec { mul := new(big.Int).Mul(d.i, i.i) - - if mul.BitLen() > maxDecBitLen { - panic("Int overflow") - } + assertMaxBitLen(mul) return BigDec{mul} } @@ -412,10 +406,7 @@ func (d BigDec) MulInt(i BigInt) BigDec { func (d BigDec) MulInt64(i int64) BigDec { bi := big.NewInt(i) mul := bi.Mul(d.i, bi) - - if mul.BitLen() > maxDecBitLen { - panic("Int overflow") - } + assertMaxBitLen(mul) return BigDec{mul} } @@ -429,38 +420,31 @@ func (d BigDec) Quo(d2 BigDec) BigDec { // mutative quotient func (d BigDec) QuoMut(d2 BigDec) BigDec { // multiply precision twice + // TODO: Use lower overhead thing here d.i.Mul(d.i, squaredPrecisionReuse) d.i.Quo(d.i, d2.i) chopPrecisionAndRound(d.i) - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } + assertMaxBitLen(d.i) return d } func (d BigDec) QuoRaw(d2 int64) BigDec { // multiply precision, so we can chop it later + // TODO: There is certainly more efficient ways to do this, come back later mul := new(big.Int).Mul(d.i, defaultBigDecPrecisionReuse) quo := mul.Quo(mul, big.NewInt(d2)) chopped := chopPrecisionAndRound(quo) - - if chopped.BitLen() > maxDecBitLen { - panic("Int overflow") - } + assertMaxBitLen(chopped) return BigDec{chopped} } // quotient truncate func (d BigDec) QuoTruncate(d2 BigDec) BigDec { - // multiply precision twice mul := new(big.Int).Mul(d.i, defaultBigDecPrecisionReuse) quo := mul.Quo(mul, d2.i) - - if quo.BitLen() > maxDecBitLen { - panic("Int overflow") - } + assertMaxBitLen(quo) return BigDec{quo} } @@ -469,10 +453,7 @@ func (d BigDec) QuoTruncateMut(d2 BigDec) BigDec { // multiply bigDec precision d.i.Mul(d.i, defaultBigDecPrecisionReuse) d.i.Quo(d.i, d2.i) - - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } + assertMaxBitLen(d.i) return d } @@ -481,10 +462,7 @@ func (d BigDec) QuoTruncateDec(d2 Dec) BigDec { // multiply Dec Precision mul := new(big.Int).Mul(d.i, precisionReuseSDKDec) quo := mul.Quo(mul, d2.BigIntMut()) - - if quo.BitLen() > maxDecBitLen { - panic("Int overflow") - } + assertMaxBitLen(quo) return BigDec{quo} } @@ -494,37 +472,53 @@ func (d BigDec) QuoTruncateDecMut(d2 Dec) BigDec { d.i.Mul(d.i, precisionReuseSDKDec) d.i.Quo(d.i, d2.BigIntMut()) - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } + assertMaxBitLen(d.i) return d } // quotient, round up func (d BigDec) QuoRoundUp(d2 BigDec) BigDec { - // multiply precision twice - mul := new(big.Int).Mul(d.i, squaredPrecisionReuse) + mul := new(big.Int).Mul(d.i, defaultBigDecPrecisionReuse) - quo := mul.Quo(mul, d2.i) - chopped := chopPrecisionAndRoundUpMut(quo, defaultBigDecPrecisionReuse) + chopped, rem := mul.QuoRem(mul, d2.i, new(big.Int)) + if rem.Sign() > 0 { + chopped.Add(chopped, oneInt) + } - if chopped.BitLen() > maxDecBitLen { - panic("Int overflow") + assertMaxBitLen(chopped) + return BigDec{chopped} +} + +// quotient, round up +func (d BigDec) QuoByDecRoundUp(d2 Dec) BigDec { + mul := new(big.Int).Mul(d.i, precisionReuseSDKDec) + + chopped, rem := mul.QuoRem(mul, d2.BigIntMut(), new(big.Int)) + if rem.Sign() > 0 { + chopped.Add(chopped, oneInt) } + + assertMaxBitLen(chopped) return BigDec{chopped} } // quotient, round up (mutative) func (d BigDec) QuoRoundUpMut(d2 BigDec) BigDec { - // multiply precision twice - d.i.Mul(d.i, squaredPrecisionReuse) - d.i.Quo(d.i, d2.i) + d.i.Mul(d.i, defaultBigDecPrecisionReuse) + _, rem := d.i.QuoRem(d.i, d2.i, new(big.Int)) - chopPrecisionAndRoundUpMut(d.i, defaultBigDecPrecisionReuse) + d.i = incBasedOnRem(rem, d.i) + assertMaxBitLen(d.i) + return BigDec{d.i} +} - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } +// quotient, round up to next integer (mutative) +func (d BigDec) QuoRoundUpNextIntMut(d2 BigDec) BigDec { + _, rem := d.i.QuoRem(d.i, d2.i, new(big.Int)) + + d.i = incBasedOnRem(rem, d.i) + d.i.Mul(d.i, defaultBigDecPrecisionReuse) + assertMaxBitLen(d.i) return BigDec{d.i} } @@ -588,6 +582,12 @@ func (d BigDec) ApproxRoot(root uint64) (guess BigDec, err error) { return guess, nil } +func assertMaxBitLen(i *big.Int) { + if i.BitLen() > maxDecBitLen { + panic("Int overflow") + } +} + // ApproxSqrt is a wrapper around ApproxRoot for the common special case // of finding the square root of a number. It returns -(sqrt(abs(d)) if input is negative. // TODO: Optimize this to be faster just using native big int sqrt. @@ -863,6 +863,13 @@ func chopPrecisionAndRoundUpDec(d *big.Int) *big.Int { return chopPrecisionAndRoundUpMut(copy, precisionReuseSDKDec) } +func incBasedOnRem(rem *big.Int, d *big.Int) *big.Int { + if rem.Sign() == 0 { + return d + } + return d.Add(d, oneInt) +} + // chopPrecisionAndRoundUp removes a Precision amount of rightmost digits and rounds up. // Mutates input d. // Mutations occur: @@ -881,12 +888,7 @@ func chopPrecisionAndRoundUpMut(d *big.Int, precisionReuse *big.Int) *big.Int { // get the truncated quotient and remainder _, rem := d.QuoRem(d, precisionReuse, big.NewInt(0)) - - if rem.Sign() == 0 { // remainder is zero - return d - } - - return d.Add(d, oneInt) + return incBasedOnRem(rem, d) } func chopPrecisionAndRoundNonMutative(d *big.Int) *big.Int { @@ -1245,6 +1247,11 @@ func (d BigDec) PowerInteger(power uint64) BigDec { func (d BigDec) PowerIntegerMut(power uint64) BigDec { if power == 0 { return OneBigDec() + } else if power == 1 { + return d + } else if power == 2 { + // save a oneBigDec allocation + return d.MulMut(d) } tmp := OneBigDec() diff --git a/osmomath/decimal_test.go b/osmomath/decimal_test.go index 9d927a7a408..bd86623b1c5 100644 --- a/osmomath/decimal_test.go +++ b/osmomath/decimal_test.go @@ -178,6 +178,75 @@ func (s *decimalTestSuite) TestNewDecFromStr() { } } +var interestingDecNumbers = []string{ + "123456789012345678901234567890123456789012345678901234567890.123456789012345678901234567890123456", + "111111111111111111111111111111111111111111111111111111111111.111111111111111111111111111111111111", + "999999999999999999999999999999999999999999999999999999999999.999999999999999999999999999999999999", + "3141592653589793238462643383279502884197169399375105820974944.592307816406286208998628034825342117", // Approximation of Pi, extended + "1618033988749894848204586834365638117720309179805762862135448.622705260462818902449707207204189391", // Approximation of Phi, extended + "2718281828459045235360287471352662497757247093699959574966967.627724076630353547594571382178525166", // Approximation of e, extended + "101010101010101010101010101010101010101010101010101010101010.101010101010101010101010101010101010", // Binary pattern extended + "1234567899876543210123456789987654321012345678998765432101234.567899876543210123456789987654321012", // Ascending and descending pattern extended + "1123581321345589144233377610987159725844181676510946173113801.986211915342546982272763642843251547", // Inspired by Fibonacci sequence, creatively adjusted + "1428571428571428571428571428571428571428571428571428571428571.428571428571428571428571428571428571", // Repeating decimal for 1/7 extended +} + +var interestingDecNumbersBigDec = []osmomath.BigDec{} +var interestingDecNumbersDec = []osmomath.Dec{} + +func init() { + for _, str := range interestingDecNumbers { + d, err := osmomath.NewBigDecFromStr(str) + if err != nil { + panic(fmt.Sprintf("error parsing decimal string %v: %v", str, err)) + } + interestingDecNumbersBigDec = append(interestingDecNumbersBigDec, d) + interestingDecNumbersDec = append(interestingDecNumbersDec, d.Dec()) + } +} + +func (s *decimalTestSuite) TestNewBigDecFromDecMulDec() { + type testcase struct { + s1, s2 osmomath.Dec + } + tests := []testcase{} + for _, d1 := range interestingDecNumbersDec { + for _, d2 := range interestingDecNumbersDec { + tests = append(tests, testcase{d1, d2}) + } + } + s.Require().True(len(tests) > 20, "no tests to run") + for _, tc := range tests { + s.Run(fmt.Sprintf("d1=%v, d2=%v", tc.s1, tc.s2), func() { + s1D := osmomath.BigDecFromDec(tc.s1) + s2D := osmomath.BigDecFromDec(tc.s2) + expected := s1D.MulMut(s2D) + actual := osmomath.NewBigDecFromDecMulDec(tc.s1, tc.s2) + s.Require().True(expected.Equal(actual), "expected %v, got %v", expected, actual) + }) + } +} + +func (s *decimalTestSuite) TestQuoRoundUpNextIntMut() { + type testcase struct { + s1, s2 osmomath.BigDec + } + tests := []testcase{} + for _, d1 := range interestingDecNumbersBigDec { + for _, d2 := range interestingDecNumbersBigDec { + tests = append(tests, testcase{d1, d2}) + } + } + s.Require().True(len(tests) > 20, "no tests to run") + for _, tc := range tests { + s.Run(fmt.Sprintf("d1=%v, d2=%v", tc.s1, tc.s2), func() { + expected := tc.s1.QuoRoundUp(tc.s2).CeilMut() + actual := tc.s1.QuoRoundUpNextIntMut(tc.s2) + s.Require().True(expected.Equal(actual), "expected %v, got %v", expected, actual) + }) + } +} + func (s *decimalTestSuite) TestDecString() { tests := []struct { d osmomath.BigDec @@ -466,6 +535,11 @@ func (s *decimalTestSuite) TestArithmetic() { s.Require().True(tc.expQuoRoundUp.Equal(resQuoRoundUp), "exp %v, res %v, tc %d", tc.expQuoRoundUp.String(), resQuoRoundUp.String(), tcIndex) + resQuoRoundUpDec := tc.d1.QuoByDecRoundUp(tc.d2.Dec()) + expResQuoRoundUpDec := tc.d1.QuoRoundUp(osmomath.BigDecFromDec(tc.d2.Dec())) + s.Require().True(expResQuoRoundUpDec.Equal(resQuoRoundUpDec), "exp %v, res %v, tc %d", + expResQuoRoundUpDec.String(), resQuoRoundUpDec.String(), tcIndex) + resQuoTruncate := tc.d1.QuoTruncate(tc.d2) s.Require().True(tc.expQuoTruncate.Equal(resQuoTruncate), "exp %v, res %v, tc %d", tc.expQuoTruncate.String(), resQuoTruncate.String(), tcIndex) diff --git a/x/concentrated-liquidity/incentives_test.go b/x/concentrated-liquidity/incentives_test.go index f61a92cc7ea..b460a1ec70b 100644 --- a/x/concentrated-liquidity/incentives_test.go +++ b/x/concentrated-liquidity/incentives_test.go @@ -3674,8 +3674,8 @@ func (s *KeeperTestSuite) TestIncentiveTruncation() { desiredCurrentSqrtPrice, err := math.TickToSqrtPrice(desiredCurrentTick) s.Require().NoError(err) - amount0 := math.CalcAmount0Delta(desiredLiquidity, desiredCurrentSqrtPrice, types.MaxSqrtPriceBigDec, true).Dec().TruncateInt() - amount1 := math.CalcAmount1Delta(desiredLiquidity, types.MinSqrtPriceBigDec, desiredCurrentSqrtPrice, true).Dec().TruncateInt() + amount0 := math.CalcAmount0Delta(desiredLiquidity.Dec(), desiredCurrentSqrtPrice, types.MaxSqrtPriceBigDec, true).Dec().TruncateInt() + amount1 := math.CalcAmount1Delta(desiredLiquidity.Dec(), types.MinSqrtPriceBigDec, desiredCurrentSqrtPrice, true).Dec().TruncateInt() lpCoins := sdk.NewCoins(sdk.NewCoin(ETH, amount0), sdk.NewCoin(USDC, amount1)) s.FundAcc(s.TestAccs[0], lpCoins) diff --git a/x/concentrated-liquidity/math/math.go b/x/concentrated-liquidity/math/math.go index de58a333ae4..f7196eafcf3 100644 --- a/x/concentrated-liquidity/math/math.go +++ b/x/concentrated-liquidity/math/math.go @@ -57,7 +57,7 @@ func Liquidity1(amount osmomath.Int, sqrtPriceA, sqrtPriceB osmomath.BigDec) osm // sqrtPriceA is the smaller of sqrtpCur and the nextPrice // sqrtPriceB is the larger of sqrtpCur and the nextPrice // CalcAmount0Delta = (liquidity * (sqrtPriceB - sqrtPriceA)) / (sqrtPriceB * sqrtPriceA) -func CalcAmount0Delta(liq, sqrtPriceA, sqrtPriceB osmomath.BigDec, roundUp bool) osmomath.BigDec { +func CalcAmount0Delta(liq osmomath.Dec, sqrtPriceA, sqrtPriceB osmomath.BigDec, roundUp bool) osmomath.BigDec { if sqrtPriceA.GT(sqrtPriceB) { sqrtPriceA, sqrtPriceB = sqrtPriceB, sqrtPriceA } @@ -79,8 +79,9 @@ func CalcAmount0Delta(liq, sqrtPriceA, sqrtPriceB osmomath.BigDec, roundUp bool) // The denominator is truncated to get a higher final amount. // Note that the order of divisions is important here. First, we divide by a larger number (sqrtPriceB) and then by a smaller number (sqrtPriceA). // This leads to a smaller error amplification. This only matters in cases where at least one of the sqrt prices is below 1. - // TODO (perf): QuoRoundUpMut with no reallocation. - return liq.MulRoundUp(diff).QuoRoundUpMut(sqrtPriceB).QuoRoundUpMut(sqrtPriceA).CeilMut() + // TODO (perf): Don't truncate after liq.MulRoundUp(diff), we actually scale by that in the next Quo. ALT: Switch liq to Dec + // TODO (perf): QuoRoundUpMut with no reallocation for internal scratch var. + return diff.MulRoundUpDec(liq).QuoRoundUpMut(sqrtPriceB).QuoRoundUpNextIntMut(sqrtPriceA) } // These are truncated at precision end to round in favor of the pool when: // - calculating amount out during swap @@ -88,20 +89,15 @@ func CalcAmount0Delta(liq, sqrtPriceA, sqrtPriceB osmomath.BigDec, roundUp bool) // Each intermediary step is truncated at precision end to get a smaller final amount. // Note that the order of divisions is important here. First, we divide by a larger number (sqrtPriceB) and then by a smaller number (sqrtPriceA). // This leads to a smaller error amplification. - return liq.MulTruncate(diff).QuoTruncateMut(sqrtPriceB).QuoTruncateMut(sqrtPriceA) + return diff.MulTruncateDec(liq).QuoTruncateMut(sqrtPriceB).QuoTruncateMut(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 // sqrtPriceA is the smaller of sqrtpCur and the nextPrice // sqrtPriceB is the larger of sqrtpCur and the nextPrice // CalcAmount1Delta = liq * (sqrtPriceB - sqrtPriceA) -func CalcAmount1Delta(liq, sqrtPriceA, sqrtPriceB osmomath.BigDec, roundUp bool) osmomath.BigDec { - // make sqrtPriceA the smaller value amongst sqrtPriceA and sqrtPriceB - // TODO: Remove this GT check and just do .AbsMut - if sqrtPriceA.GT(sqrtPriceB) { - sqrtPriceA, sqrtPriceB = sqrtPriceB, sqrtPriceA - } - diff := sqrtPriceB.Sub(sqrtPriceA) +func CalcAmount1Delta(liq osmomath.Dec, sqrtPriceA, sqrtPriceB osmomath.BigDec, roundUp bool) osmomath.BigDec { + diff := sqrtPriceB.Sub(sqrtPriceA).AbsMut() // if calculating for amountIn, we round up // if calculating for amountOut, we don't round at all // this is to prevent removing more from the pool than expected due to rounding @@ -115,13 +111,14 @@ func CalcAmount1Delta(liq, sqrtPriceA, sqrtPriceB osmomath.BigDec, roundUp bool) // Examples include: // - calculating amountIn during swap // - adding liquidity (request user to provide more tokens in in favor of the pool) - return liq.Mul(diff).CeilMut() + // TODO: Make a MulDecCeilMut to save more internal ops + return diff.MulDecMut(liq).CeilMut() } // This is truncated at precision end to round in favor of the pool when: // - calculating amount out during swap // - withdrawing liquidity // The denominator is rounded up to get a higher final amount. - return liq.MulTruncate(diff) + return diff.MulTruncateDec(liq) } // GetNextSqrtPriceFromAmount0InRoundingUp utilizes sqrtPriceCurrent, liquidity, and amount of denom0 that still needs @@ -148,13 +145,13 @@ func GetNextSqrtPriceFromAmount0InRoundingUp(sqrtPriceCurrent, liquidity, amount // When we swap for token one in given token zero out, the price is increasing and we need to move the price up enough // so that we get the desired output amount out. Therefore, we round up. // sqrt_next = liq * sqrt_cur / (liq - token_out * sqrt_cur) -func GetNextSqrtPriceFromAmount0OutRoundingUp(sqrtPriceCurrent, liquidity, amountZeroRemainingOut osmomath.BigDec) (sqrtPriceNext osmomath.BigDec) { +func GetNextSqrtPriceFromAmount0OutRoundingUp(sqrtPriceCurrent, liquidity osmomath.BigDec, amountZeroRemainingOut osmomath.Dec) (sqrtPriceNext osmomath.BigDec) { if amountZeroRemainingOut.IsZero() { return sqrtPriceCurrent } // mul round up to make the final denominator smaller and final result larger - product := amountZeroRemainingOut.MulRoundUp(sqrtPriceCurrent) + product := sqrtPriceCurrent.MulRoundUpDec(amountZeroRemainingOut) denominator := liquidity.Sub(product) // mul round up numerator to make the final result larger // quo round up to make the final result larger @@ -166,8 +163,8 @@ func GetNextSqrtPriceFromAmount0OutRoundingUp(sqrtPriceCurrent, liquidity, amoun // When we swap for token zero out given token one in, the price is increasing and we need to move the sqrt price (increase it) less to // avoid overpaying out of the pool. Therefore, we round down. // sqrt_next = sqrt_cur + token_in / liq -func GetNextSqrtPriceFromAmount1InRoundingDown(sqrtPriceCurrent, liquidity, amountOneRemainingIn osmomath.BigDec) (sqrtPriceNext osmomath.BigDec) { - return sqrtPriceCurrent.Add(amountOneRemainingIn.QuoTruncate(liquidity)) +func GetNextSqrtPriceFromAmount1InRoundingDown(sqrtPriceCurrent osmomath.BigDec, liquidity osmomath.Dec, amountOneRemainingIn osmomath.BigDec) (sqrtPriceNext osmomath.BigDec) { + return amountOneRemainingIn.QuoTruncateDec(liquidity).AddMut(sqrtPriceCurrent) } // GetNextSqrtPriceFromAmount1OutRoundingDown utilizes the current sqrtPriceCurrent, liquidity, and amount of denom1 that still needs @@ -175,8 +172,8 @@ func GetNextSqrtPriceFromAmount1InRoundingDown(sqrtPriceCurrent, liquidity, amou // When we swap for token zero in given token one out, the price is decrearing and we need to move the price down enough // so that we get the desired output amount out. // sqrt_next = sqrt_cur - token_out / liq -func GetNextSqrtPriceFromAmount1OutRoundingDown(sqrtPriceCurrent, liquidity, amountOneRemainingOut osmomath.BigDec) (sqrtPriceNext osmomath.BigDec) { - return sqrtPriceCurrent.Sub(amountOneRemainingOut.QuoRoundUp(liquidity)) +func GetNextSqrtPriceFromAmount1OutRoundingDown(sqrtPriceCurrent osmomath.BigDec, liquidity osmomath.Dec, amountOneRemainingOut osmomath.BigDec) (sqrtPriceNext osmomath.BigDec) { + return sqrtPriceCurrent.Sub(amountOneRemainingOut.QuoByDecRoundUp(liquidity)) } // GetLiquidityFromAmounts takes the current sqrtPrice and the sqrtPrice for the upper and lower ticks as well as the amounts of asset0 and asset1 diff --git a/x/concentrated-liquidity/math/math_test.go b/x/concentrated-liquidity/math/math_test.go index c8ed14afe8b..bcf13685ddb 100644 --- a/x/concentrated-liquidity/math/math_test.go +++ b/x/concentrated-liquidity/math/math_test.go @@ -124,7 +124,7 @@ func TestLiquidity0(t *testing.T) { // calcAmount0Delta = (liquidity * (sqrtPriceB - sqrtPriceA)) / (sqrtPriceB * sqrtPriceA) func TestCalcAmount0Delta(t *testing.T) { testCases := map[string]struct { - liquidity osmomath.BigDec + liquidity osmomath.Dec sqrtPA osmomath.BigDec sqrtPB osmomath.BigDec isWithTolerance bool @@ -132,16 +132,16 @@ func TestCalcAmount0Delta(t *testing.T) { amount0Expected osmomath.BigDec }{ "happy path": { - liquidity: osmomath.MustNewBigDecFromStr("1517882343.751510418088349649"), // we use the smaller liquidity between liq0 and liq1 - sqrtPA: sqrt5000BigDec, // 5000 - sqrtPB: sqrt5500BigDec, // 5500 + liquidity: osmomath.MustNewDecFromStr("1517882343.751510418088349649"), // we use the smaller liquidity between liq0 and liq1 + sqrtPA: sqrt5000BigDec, // 5000 + sqrtPB: sqrt5500BigDec, // 5500 roundUp: false, // calculated with x/concentrated-liquidity/python/clmath.py round_decimal(amount0, 36, ROUND_FLOOR) amount0Expected: osmomath.MustNewBigDecFromStr("998976.618347426388356629926969277767437533"), // truncated at precision end. isWithTolerance: false, }, "happy path, sqrtPriceA greater than sqrtPrice B": { // commute prior vector - liquidity: osmomath.MustNewBigDecFromStr("1517882343.751510418088349649"), + liquidity: osmomath.MustNewDecFromStr("1517882343.751510418088349649"), sqrtPA: sqrt5500BigDec, sqrtPB: sqrt5000BigDec, roundUp: false, @@ -160,7 +160,7 @@ func TestCalcAmount0Delta(t *testing.T) { // min_sqrt_p = Decimal("0.000000152731791058") // liq = Decimal("931361973132462178951297") // liq * (max_sqrt_p - min_sqrt_p) / (max_sqrt_p * min_sqrt_p) - liquidity: osmomath.MustNewBigDecFromStr("931361973132462178951297"), + liquidity: osmomath.MustNewDecFromStr("931361973132462178951297"), // price: 0.000000000000023327 sqrtPA: osmomath.MustNewBigDecFromStr("0.000000152731791058"), // price: 952361284325389721913 @@ -181,7 +181,7 @@ func TestCalcAmount0Delta(t *testing.T) { // min_sqrt_p = Decimal("0.000000152731791058") // liq = Decimal("931361973132462178951297") // liq * (max_sqrt_p - min_sqrt_p) / (max_sqrt_p * min_sqrt_p) - liquidity: osmomath.MustNewBigDecFromStr("931361973132462178951297"), + liquidity: osmomath.MustNewDecFromStr("931361973132462178951297"), // price: 0.000000000000023327 sqrtPA: osmomath.MustNewBigDecFromStr("0.000000152731791058"), // price: 952361284325389721913 @@ -200,7 +200,7 @@ func TestCalcAmount0Delta(t *testing.T) { // min_sqrt_p = Decimal("0.000000000000001409841835100661211756") // liq = Decimal("5000252259822539816806336.971796256914465071095518135400579243") // liq * (max_sqrt_p - min_sqrt_p) / (max_sqrt_p * min_sqrt_p) - liquidity: osmomath.MustNewBigDecFromStr("5000252259822539816806336.971796256914465071095518135400579243"), + liquidity: osmomath.MustNewDecFromStr("5000252259822539816806336.971796256914465071"), sqrtPA: osmomath.MustNewBigDecFromStr("0.00000099994999874993749609347654199"), sqrtPB: osmomath.MustNewBigDecFromStr("0.000000000000001409841835100661211756"), roundUp: true, @@ -216,7 +216,7 @@ func TestCalcAmount0Delta(t *testing.T) { // min_sqrt_p = Decimal("0.000000000000001409841835100661211756") // liq = Decimal("5000252259822539816806336.971796256914465071095518135400579243") // liq * (max_sqrt_p - min_sqrt_p) / (max_sqrt_p * min_sqrt_p) - liquidity: osmomath.MustNewBigDecFromStr("5000252259822539816806336.971796256914465071095518135400579243"), + liquidity: osmomath.MustNewDecFromStr("5000252259822539816806336.971796256914465071"), sqrtPA: osmomath.MustNewBigDecFromStr("0.00000099994999874993749609347654199"), sqrtPB: osmomath.MustNewBigDecFromStr("0.000000000000001409841835100661211756"), roundUp: false, @@ -224,14 +224,14 @@ func TestCalcAmount0Delta(t *testing.T) { amount0Expected: osmomath.MustNewBigDecFromStr("3546676037185128488234786333758360815266.999539026068480181194797910898392880"), }, "low price range": { - liquidity: smallLiquidity, + liquidity: smallLiquidity.Dec(), sqrtPA: sqrtANearMin, sqrtPB: sqrtBNearMin, roundUp: false, // from clmath decimal import * // from math import * // calc_amount_zero_delta(liq, sqrtPriceA, sqrtPriceB, False) - amount0Expected: osmomath.MustNewBigDecFromStr("12399.405290456300691064448232516066947340"), + amount0Expected: osmomath.MustNewBigDecFromStr("12399.403617882634341191547243098659145924"), }, } @@ -267,7 +267,7 @@ func TestCalcAmount0Delta(t *testing.T) { // calcAmount1Delta = liq * (sqrtPriceB - sqrtPriceA) func TestCalcAmount1Delta(t *testing.T) { testCases := map[string]struct { - liquidity osmomath.BigDec + liquidity osmomath.Dec sqrtPA osmomath.BigDec sqrtPB osmomath.BigDec exactEqual bool @@ -275,9 +275,9 @@ func TestCalcAmount1Delta(t *testing.T) { amount1Expected osmomath.BigDec }{ "round down": { - liquidity: osmomath.MustNewBigDecFromStr("1517882343.751510418088349649"), // we use the smaller liquidity between liq0 and liq1 - sqrtPA: sqrt5000BigDec, // 5000 - sqrtPB: sqrt4545BigDec, // 4545 + liquidity: osmomath.MustNewDecFromStr("1517882343.751510418088349649"), // we use the smaller liquidity between liq0 and liq1 + sqrtPA: sqrt5000BigDec, // 5000 + sqrtPB: sqrt4545BigDec, // 4545 roundUp: false, // calculated with x/concentrated-liquidity/python/clmath.py amount1Expected: osmomath.MustNewBigDecFromStr("4999999999.999999999999999999696837821702147054"), @@ -292,7 +292,7 @@ func TestCalcAmount1Delta(t *testing.T) { // min_sqrt_p = Decimal("0.000000152731791058") // liq = Decimal("931361973132462178951297") // liq * (max_sqrt_p - min_sqrt_p) - liquidity: osmomath.MustNewBigDecFromStr("931361973132462178951297"), + liquidity: osmomath.MustNewDecFromStr("931361973132462178951297"), // price: 0.000000000000023327 sqrtPA: osmomath.MustNewBigDecFromStr("0.000000152731791058"), // price: 952361284325389721913 @@ -311,7 +311,7 @@ func TestCalcAmount1Delta(t *testing.T) { // min_sqrt_p = Decimal("0.000000152731791058") // liq = Decimal("931361973132462178951297") // liq * (max_sqrt_p - min_sqrt_p) - liquidity: osmomath.MustNewBigDecFromStr("931361973132462178951297"), + liquidity: osmomath.MustNewDecFromStr("931361973132462178951297"), // price: 0.000000000000023327 sqrtPA: osmomath.MustNewBigDecFromStr("0.000000152731791058"), // price: 952361284325389721913 @@ -320,24 +320,24 @@ func TestCalcAmount1Delta(t *testing.T) { amount1Expected: osmomath.MustNewBigDecFromStr("28742157707995443393876876754535992.801567623738751734").Ceil(), // round up at precision end. }, "low price range (no round up)": { - liquidity: smallLiquidity, + liquidity: smallLiquidity.Dec(), sqrtPA: sqrtANearMin, sqrtPB: sqrtBNearMin, roundUp: false, // from clmath decimal import * // from math import * // calc_amount_one_delta(liq, sqrtPriceA, sqrtPriceB, False) - amount1Expected: osmomath.MustNewBigDecFromStr("0.000000000000000000000000000103787162"), + amount1Expected: osmomath.MustNewBigDecFromStr("0.000000000000000000000000000103787148"), }, "low price range (with round up)": { - liquidity: smallLiquidity, + liquidity: smallLiquidity.Dec(), sqrtPA: sqrtANearMin, sqrtPB: sqrtBNearMin, roundUp: true, // from clmath decimal import * // calc_amount_one_delta(liq, sqrtPriceA, sqrtPriceB, False) - // Actual result: 0.000000000000000000000000000103787163 - amount1Expected: osmomath.MustNewBigDecFromStr("0.000000000000000000000000000103787163").Ceil(), + // Actual result: 0.000000000000000000000000000103787149 + amount1Expected: osmomath.MustNewBigDecFromStr("0.000000000000000000000000000103787149").Ceil(), }, } @@ -472,6 +472,20 @@ type sqrtRoundingTestCase struct { expected osmomath.BigDec } +type sqrtRoundingDecTestCase struct { + sqrtPriceCurrent osmomath.BigDec + liquidity osmomath.Dec + amountRemaining osmomath.BigDec + expected osmomath.BigDec +} + +type sqrtRoundingAmtDecTestCase struct { + sqrtPriceCurrent osmomath.BigDec + liquidity osmomath.BigDec + amountRemaining osmomath.Dec + expected osmomath.BigDec +} + func runSqrtRoundingTestCase( t *testing.T, name string, @@ -487,6 +501,36 @@ func runSqrtRoundingTestCase( } } +func runSqrtRoundingDecTestCase( + t *testing.T, + name string, + fn func(osmomath.BigDec, osmomath.Dec, osmomath.BigDec) osmomath.BigDec, + cases map[string]sqrtRoundingDecTestCase, +) { + for name, tc := range cases { + tc := tc + t.Run(name, func(t *testing.T) { + sqrtPriceNext := fn(tc.sqrtPriceCurrent, tc.liquidity, tc.amountRemaining) + require.Equal(t, tc.expected.String(), sqrtPriceNext.String()) + }) + } +} + +func runSqrtRoundingAmtDecTestCase( + t *testing.T, + name string, + fn func(osmomath.BigDec, osmomath.BigDec, osmomath.Dec) osmomath.BigDec, + cases map[string]sqrtRoundingAmtDecTestCase, +) { + for name, tc := range cases { + tc := tc + t.Run(name, func(t *testing.T) { + sqrtPriceNext := fn(tc.sqrtPriceCurrent, tc.liquidity, tc.amountRemaining) + require.Equal(t, tc.expected.String(), sqrtPriceNext.String()) + }) + } +} + // Estimates are computed with x/concentrated-liquidity/python/clmath.py func TestGetNextSqrtPriceFromAmount0InRoundingUp(t *testing.T) { tests := map[string]sqrtRoundingTestCase{ @@ -525,88 +569,88 @@ func TestGetNextSqrtPriceFromAmount0InRoundingUp(t *testing.T) { // Estimates are computed with x/concentrated-liquidity/python/clmath.py func TestGetNextSqrtPriceFromAmount0OutRoundingUp(t *testing.T) { - tests := map[string]sqrtRoundingTestCase{ + tests := map[string]sqrtRoundingAmtDecTestCase{ "rounded up at precision end": { sqrtPriceCurrent: sqrt5000BigDec, liquidity: osmomath.MustNewBigDecFromStr("3035764687.503020836176699298"), - amountRemaining: osmomath.MustNewBigDecFromStr("8398"), + amountRemaining: osmomath.MustNewDecFromStr("8398"), // get_next_sqrt_price_from_amount0_out_round_up(liquidity,sqrtPriceCurrent ,amountRemaining) expected: osmomath.MustNewBigDecFromStr("70.724512595179305565323229510645063950"), }, "no round up due zeroes at precision end": { sqrtPriceCurrent: osmomath.MustNewBigDecFromStr("2"), liquidity: osmomath.MustNewBigDecFromStr("10"), - amountRemaining: osmomath.MustNewBigDecFromStr("1"), + amountRemaining: osmomath.MustNewDecFromStr("1"), // liq * sqrt_cur / (liq + token_out * sqrt_cur) = 2.5 expected: osmomath.MustNewBigDecFromStr("2.5"), }, "low price range": { liquidity: smallLiquidity, sqrtPriceCurrent: sqrtANearMin, - amountRemaining: smallValue, + amountRemaining: smallValue.Dec(), // from clmath decimal import * // get_next_sqrt_price_from_amount0_out_round_up(liq, sqrtPriceA, amountRemaining) expected: osmomath.MustNewBigDecFromStr("0.000000000000000023829902587267894423"), }, } - runSqrtRoundingTestCase(t, "TestGetNextSqrtPriceFromAmount0OutRoundingUp", math.GetNextSqrtPriceFromAmount0OutRoundingUp, tests) + runSqrtRoundingAmtDecTestCase(t, "TestGetNextSqrtPriceFromAmount0OutRoundingUp", math.GetNextSqrtPriceFromAmount0OutRoundingUp, tests) } // Estimates are computed with x/concentrated-liquidity/python/clmath.py func TestGetNextSqrtPriceFromAmount1InRoundingDown(t *testing.T) { - tests := map[string]sqrtRoundingTestCase{ + tests := map[string]sqrtRoundingDecTestCase{ "rounded down at precision end": { sqrtPriceCurrent: sqrt5000BigDec, - liquidity: osmomath.MustNewBigDecFromStr("3035764687.503020836176699298"), + liquidity: osmomath.MustNewDecFromStr("3035764687.503020836176699298"), amountRemaining: osmomath.MustNewBigDecFromStr("8398"), expected: osmomath.MustNewBigDecFromStr("70.710680885008822823343339270800000167"), }, "no round up due zeroes at precision end": { sqrtPriceCurrent: osmomath.MustNewBigDecFromStr("2.5"), - liquidity: osmomath.MustNewBigDecFromStr("1"), + liquidity: osmomath.OneDec(), amountRemaining: osmomath.MustNewBigDecFromStr("10"), // sqrt_next = sqrt_cur + token_in / liq expected: osmomath.MustNewBigDecFromStr("12.5"), }, "happy path": { - liquidity: osmomath.MustNewBigDecFromStr("1519437308.014768571721000000"), // liquidity1 calculated above - sqrtPriceCurrent: sqrt5000BigDec, // 5000000000 + liquidity: osmomath.MustNewDecFromStr("1519437308.014768571721000000"), // liquidity1 calculated above + sqrtPriceCurrent: sqrt5000BigDec, // 5000000000 amountRemaining: osmomath.NewBigDec(42000000), // sqrt_next = sqrt_cur + token_in / liq // calculated with x/concentrated-liquidity/python/clmath.py round_decimal(sqrt_next, 36, ROUND_FLOOR) expected: osmomath.MustNewBigDecFromStr("70.738319930382329008049494613660784220"), }, "low price range": { - liquidity: smallLiquidity, + liquidity: smallLiquidity.Dec(), sqrtPriceCurrent: sqrtANearMin, amountRemaining: smallValue, // from clmath decimal import * // get_next_sqrt_price_from_amount1_in_round_down(liq, sqrtPriceA, amountRemaining) - expected: osmomath.MustNewBigDecFromStr("31964936923603.477920799226065501544948016880497639"), + expected: osmomath.MustNewBigDecFromStr("31964941472737.900293161392817774305123129525585219"), }, } - runSqrtRoundingTestCase(t, "TestGetNextSqrtPriceFromAmount1InRoundingDown", math.GetNextSqrtPriceFromAmount1InRoundingDown, tests) + runSqrtRoundingDecTestCase(t, "TestGetNextSqrtPriceFromAmount1InRoundingDown", math.GetNextSqrtPriceFromAmount1InRoundingDown, tests) } func TestGetNextSqrtPriceFromAmount1OutRoundingDown(t *testing.T) { - tests := map[string]sqrtRoundingTestCase{ + tests := map[string]sqrtRoundingDecTestCase{ "rounded down at precision end": { sqrtPriceCurrent: sqrt5000BigDec, - liquidity: osmomath.MustNewBigDecFromStr("3035764687.503020836176699298"), + liquidity: osmomath.MustNewDecFromStr("3035764687.503020836176699298"), amountRemaining: osmomath.MustNewBigDecFromStr("8398"), // round_osmo_prec_down(sqrtPriceCurrent - round_osmo_prec_up(tokenOut / liquidity)) expected: osmomath.MustNewBigDecFromStr("70.710675352300682056656660729199999832"), }, "no round up due zeroes at precision end": { sqrtPriceCurrent: osmomath.MustNewBigDecFromStr("12.5"), - liquidity: osmomath.MustNewBigDecFromStr("1"), + liquidity: osmomath.MustNewDecFromStr("1"), amountRemaining: osmomath.MustNewBigDecFromStr("10"), // round_osmo_prec_down(sqrtPriceCurrent - round_osmo_prec_up(tokenOut / liquidity)) expected: osmomath.MustNewBigDecFromStr("2.5"), }, "low price range": { - liquidity: smallLiquidity, + liquidity: smallLiquidity.Dec(), sqrtPriceCurrent: sqrtANearMin, amountRemaining: smallValue, // from clmath decimal import * @@ -614,8 +658,8 @@ func TestGetNextSqrtPriceFromAmount1OutRoundingDown(t *testing.T) { // While a negative sqrt price value is invalid and should be caught by the caller, // we mostly focus on testing rounding behavior and math correctness at low spot prices. // For the purposes of our test, this result is acceptable. - expected: osmomath.MustNewBigDecFromStr("-31964936923603.477920799226065453921424417717867010"), + expected: osmomath.MustNewBigDecFromStr("-31964941472737.900293161392817726681599530362954590"), }, } - runSqrtRoundingTestCase(t, "TestGetNextSqrtPriceFromAmount1OutRoundingDown", math.GetNextSqrtPriceFromAmount1OutRoundingDown, tests) + runSqrtRoundingDecTestCase(t, "TestGetNextSqrtPriceFromAmount1OutRoundingDown", math.GetNextSqrtPriceFromAmount1OutRoundingDown, tests) } diff --git a/x/concentrated-liquidity/model/pool.go b/x/concentrated-liquidity/model/pool.go index 556a6a9c175..53f715c946c 100644 --- a/x/concentrated-liquidity/model/pool.go +++ b/x/concentrated-liquidity/model/pool.go @@ -125,7 +125,7 @@ func (p Pool) SpotPrice(ctx sdk.Context, quoteAssetDenom string, baseAssetDenom if baseAssetDenom == p.Token0 { return osmomath.BigDecFromDecMut(priceSquared.Dec()), nil } - return osmomath.BigDecFromDecMut(osmomath.OneBigDec().Quo(priceSquared).Dec()), nil + return osmomath.BigDecFromDecMut(osmomath.OneBigDec().QuoMut(priceSquared).Dec()), nil } // GetToken0 returns the token0 of the pool @@ -246,9 +246,8 @@ func (p Pool) CalcActualAmounts(ctx sdk.Context, lowerTick, upperTick int64, liq roundUp := liquidityDelta.IsPositive() var ( - liquidityDeltaBigDec = osmomath.BigDecFromDec(liquidityDelta) - actualAmountDenom0 osmomath.BigDec - actualAmountDenom1 osmomath.BigDec + actualAmountDenom0 osmomath.BigDec + actualAmountDenom1 osmomath.BigDec ) if p.IsCurrentTickInRange(lowerTick, upperTick) { @@ -256,18 +255,18 @@ func (p Pool) CalcActualAmounts(ctx sdk.Context, lowerTick, upperTick int64, liq // if this is the case, we attempt to provide liquidity evenly between asset0 and asset1 // we also update the pool liquidity since the virtual liquidity is modified by this position's creation currentSqrtPrice := p.CurrentSqrtPrice - actualAmountDenom0 = math.CalcAmount0Delta(liquidityDeltaBigDec, currentSqrtPrice, sqrtPriceUpperTick, roundUp) - actualAmountDenom1 = math.CalcAmount1Delta(liquidityDeltaBigDec, currentSqrtPrice, sqrtPriceLowerTick, roundUp) + actualAmountDenom0 = math.CalcAmount0Delta(liquidityDelta, currentSqrtPrice, sqrtPriceUpperTick, roundUp) + actualAmountDenom1 = math.CalcAmount1Delta(liquidityDelta, currentSqrtPrice, sqrtPriceLowerTick, roundUp) } else if p.CurrentTick < lowerTick { // outcome two: position is below current price // this means position is solely made up of asset0 actualAmountDenom1 = osmomath.ZeroBigDec() - actualAmountDenom0 = math.CalcAmount0Delta(liquidityDeltaBigDec, sqrtPriceLowerTick, sqrtPriceUpperTick, roundUp) + actualAmountDenom0 = math.CalcAmount0Delta(liquidityDelta, sqrtPriceLowerTick, sqrtPriceUpperTick, roundUp) } else { // outcome three: position is above current price // this means position is solely made up of asset1 actualAmountDenom0 = osmomath.ZeroBigDec() - actualAmountDenom1 = math.CalcAmount1Delta(liquidityDeltaBigDec, sqrtPriceLowerTick, sqrtPriceUpperTick, roundUp) + actualAmountDenom1 = math.CalcAmount1Delta(liquidityDelta, sqrtPriceLowerTick, sqrtPriceUpperTick, roundUp) } if roundUp { diff --git a/x/concentrated-liquidity/model/pool_test.go b/x/concentrated-liquidity/model/pool_test.go index 09dff9df73c..0a33ec67284 100644 --- a/x/concentrated-liquidity/model/pool_test.go +++ b/x/concentrated-liquidity/model/pool_test.go @@ -574,8 +574,7 @@ func (suite *ConcentratedPoolTestSuite) TestCalcActualAmounts() { return sqrtPrice } - defaultLiquidityDelta = osmomath.NewDec(1000) - defaultLiquidityDeltaBigDec = osmomath.NewBigDec(1000) + defaultLiquidityDelta = osmomath.NewDec(1000) lowerTick = int64(-99) lowerSqrtPriceBigDec = tickToSqrtPrice(lowerTick) @@ -605,8 +604,8 @@ func (suite *ConcentratedPoolTestSuite) TestCalcActualAmounts() { liquidityDelta: defaultLiquidityDelta, shouldTestRoundingInvariant: true, - expectedAmount0: clmath.CalcAmount0Delta(defaultLiquidityDeltaBigDec, midSqrtPriceBigDec, upperSqrtPriceBigDec, true).Dec(), - expectedAmount1: clmath.CalcAmount1Delta(defaultLiquidityDeltaBigDec, midSqrtPriceBigDec, lowerSqrtPriceBigDec, true).Dec(), + expectedAmount0: clmath.CalcAmount0Delta(defaultLiquidityDelta, midSqrtPriceBigDec, upperSqrtPriceBigDec, true).Dec(), + expectedAmount1: clmath.CalcAmount1Delta(defaultLiquidityDelta, midSqrtPriceBigDec, lowerSqrtPriceBigDec, true).Dec(), }, "current in range, negative liquidity": { currentTick: midtick, @@ -614,8 +613,8 @@ func (suite *ConcentratedPoolTestSuite) TestCalcActualAmounts() { upperTick: uppertick, liquidityDelta: defaultLiquidityDelta.Neg(), - expectedAmount0: clmath.CalcAmount0Delta(defaultLiquidityDeltaBigDec.Neg(), midSqrtPriceBigDec, upperSqrtPriceBigDec, false).Dec(), - expectedAmount1: clmath.CalcAmount1Delta(defaultLiquidityDeltaBigDec.Neg(), midSqrtPriceBigDec, lowerSqrtPriceBigDec, false).Dec(), + expectedAmount0: clmath.CalcAmount0Delta(defaultLiquidityDelta.Neg(), midSqrtPriceBigDec, upperSqrtPriceBigDec, false).Dec(), + expectedAmount1: clmath.CalcAmount1Delta(defaultLiquidityDelta.Neg(), midSqrtPriceBigDec, lowerSqrtPriceBigDec, false).Dec(), }, "current below range, positive liquidity": { currentTick: lowerTick, @@ -623,7 +622,7 @@ func (suite *ConcentratedPoolTestSuite) TestCalcActualAmounts() { upperTick: uppertick, liquidityDelta: defaultLiquidityDelta, - expectedAmount0: clmath.CalcAmount0Delta(defaultLiquidityDeltaBigDec, midSqrtPriceBigDec, upperSqrtPriceBigDec, true).Dec(), + expectedAmount0: clmath.CalcAmount0Delta(defaultLiquidityDelta, midSqrtPriceBigDec, upperSqrtPriceBigDec, true).Dec(), expectedAmount1: osmomath.ZeroDec(), }, "current below range, negative liquidity": { @@ -632,7 +631,7 @@ func (suite *ConcentratedPoolTestSuite) TestCalcActualAmounts() { upperTick: uppertick, liquidityDelta: defaultLiquidityDelta.Neg(), - expectedAmount0: clmath.CalcAmount0Delta(defaultLiquidityDeltaBigDec.Neg(), midSqrtPriceBigDec, upperSqrtPriceBigDec, false).Dec(), + expectedAmount0: clmath.CalcAmount0Delta(defaultLiquidityDelta.Neg(), midSqrtPriceBigDec, upperSqrtPriceBigDec, false).Dec(), expectedAmount1: osmomath.ZeroDec(), }, "current above range, positive liquidity": { @@ -642,7 +641,7 @@ func (suite *ConcentratedPoolTestSuite) TestCalcActualAmounts() { liquidityDelta: defaultLiquidityDelta, expectedAmount0: osmomath.ZeroDec(), - expectedAmount1: clmath.CalcAmount1Delta(defaultLiquidityDeltaBigDec, lowerSqrtPriceBigDec, midSqrtPriceBigDec, true).Dec(), + expectedAmount1: clmath.CalcAmount1Delta(defaultLiquidityDelta, lowerSqrtPriceBigDec, midSqrtPriceBigDec, true).Dec(), }, "current above range, negative liquidity": { currentTick: uppertick, @@ -651,7 +650,7 @@ func (suite *ConcentratedPoolTestSuite) TestCalcActualAmounts() { liquidityDelta: defaultLiquidityDelta.Neg(), expectedAmount0: osmomath.ZeroDec(), - expectedAmount1: clmath.CalcAmount1Delta(defaultLiquidityDeltaBigDec.Neg(), lowerSqrtPriceBigDec, midSqrtPriceBigDec, false).Dec(), + expectedAmount1: clmath.CalcAmount1Delta(defaultLiquidityDelta.Neg(), lowerSqrtPriceBigDec, midSqrtPriceBigDec, false).Dec(), }, // errors diff --git a/x/concentrated-liquidity/position_test.go b/x/concentrated-liquidity/position_test.go index e7ffdd71d0c..917bd2b1813 100644 --- a/x/concentrated-liquidity/position_test.go +++ b/x/concentrated-liquidity/position_test.go @@ -2038,7 +2038,7 @@ func (s *KeeperTestSuite) TestNegativeTickRange_SpreadFactor() { s.Require().True(toTick < pool.GetCurrentTick()) - amountZeroIn := math.CalcAmount0Delta(osmomath.BigDecFromDec(pool.GetLiquidity()), pool.GetCurrentSqrtPrice(), s.tickToSqrtPrice(toTick), true) + amountZeroIn := math.CalcAmount0Delta(pool.GetLiquidity(), pool.GetCurrentSqrtPrice(), s.tickToSqrtPrice(toTick), true) coinZeroIn := sdk.NewCoin(denom0, amountZeroIn.Dec().TruncateInt()) return coinZeroIn @@ -2054,7 +2054,7 @@ func (s *KeeperTestSuite) TestNegativeTickRange_SpreadFactor() { s.Require().True(toTick > pool.GetCurrentTick()) - amountOneIn := math.CalcAmount1Delta(osmomath.BigDecFromDec(pool.GetLiquidity()), pool.GetCurrentSqrtPrice(), s.tickToSqrtPrice(toTick), true) + amountOneIn := math.CalcAmount1Delta(pool.GetLiquidity(), pool.GetCurrentSqrtPrice(), s.tickToSqrtPrice(toTick), true) coinOneIn := sdk.NewCoin(denom1, amountOneIn.Dec().TruncateInt()) return coinOneIn diff --git a/x/concentrated-liquidity/swaps_tick_cross_test.go b/x/concentrated-liquidity/swaps_tick_cross_test.go index 814c1db9082..5613b892bdf 100644 --- a/x/concentrated-liquidity/swaps_tick_cross_test.go +++ b/x/concentrated-liquidity/swaps_tick_cross_test.go @@ -293,7 +293,7 @@ func (s *KeeperTestSuite) computeSwapAmounts(poolId uint64, curSqrtPrice osmomat var isWithinDesiredBucketAfterSwap bool if isZeroForOne { // Round up so that we cross the tick by default. - curAmountIn := math.CalcAmount0Delta(osmomath.BigDecFromDec(currentLiquidity), curSqrtPrice, nextInitTickSqrtPrice, true).DecRoundUp() + curAmountIn := math.CalcAmount0Delta(currentLiquidity, curSqrtPrice, nextInitTickSqrtPrice, true).DecRoundUp() amountIn = amountIn.Add(curAmountIn) @@ -316,12 +316,12 @@ func (s *KeeperTestSuite) computeSwapAmounts(poolId uint64, curSqrtPrice osmomat nextInitTickSqrtPrice := s.tickToSqrtPrice(liquidityNetAmounts[i+1].TickIndex) // We discount by half so that we do no cross any tick and remain in the same bucket. - curAmountIn := math.CalcAmount0Delta(osmomath.BigDecFromDec(currentLiquidity), curSqrtPrice, nextInitTickSqrtPrice, true).QuoInt64(2).DecRoundUp() + curAmountIn := math.CalcAmount0Delta(currentLiquidity, curSqrtPrice, nextInitTickSqrtPrice, true).QuoInt64(2).DecRoundUp() amountIn = amountIn.Add(curAmountIn) } } else { // Round up so that we cross the tick by default. - curAmountIn := math.CalcAmount1Delta(osmomath.BigDecFromDec(currentLiquidity), curSqrtPrice, nextInitTickSqrtPrice, true).Dec() + curAmountIn := math.CalcAmount1Delta(currentLiquidity, curSqrtPrice, nextInitTickSqrtPrice, true).Dec() amountIn = amountIn.Add(curAmountIn) // The tick should be crossed if currentTick <= expectedTickToSwapTo, unless the intention @@ -346,7 +346,7 @@ func (s *KeeperTestSuite) computeSwapAmounts(poolId uint64, curSqrtPrice osmomat return amountIn, currentLiquidity, curSqrtPrice } -func (s *KeeperTestSuite) computeSwapAmountsInGivenOut(poolId uint64, curSqrtPrice osmomath.BigDec, expectedTickToSwapTo int64, isZeroForOne bool, shouldStayWithinTheSameBucket bool) (osmomath.Dec, osmomath.BigDec, osmomath.BigDec) { +func (s *KeeperTestSuite) computeSwapAmountsInGivenOut(poolId uint64, curSqrtPrice osmomath.BigDec, expectedTickToSwapTo int64, isZeroForOne bool, shouldStayWithinTheSameBucket bool) (osmomath.Dec, osmomath.Dec, osmomath.BigDec) { pool, err := s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, poolId) s.Require().NoError(err) @@ -368,7 +368,7 @@ func (s *KeeperTestSuite) computeSwapAmountsInGivenOut(poolId uint64, curSqrtPri } // Start from current pool liquidity and zero amount in. - currentLiquidity := osmomath.BigDecFromDec(pool.GetLiquidity()) + currentLiquidity := pool.GetLiquidity() amountOut := osmomath.ZeroDec() for i, liquidityNetEntry := range liquidityNetAmounts { @@ -391,7 +391,7 @@ func (s *KeeperTestSuite) computeSwapAmountsInGivenOut(poolId uint64, curSqrtPri if shouldCrossTick { // Runs regular tick crossing logic. curSqrtPrice = s.tickToSqrtPrice(nextInitializedTick) - currentLiquidity = currentLiquidity.Sub(osmomath.BigDecFromDec(liquidityNetEntry.LiquidityNet)) + currentLiquidity = currentLiquidity.Sub(liquidityNetEntry.LiquidityNet) currentTick = nextInitializedTick - 1 } @@ -418,7 +418,7 @@ func (s *KeeperTestSuite) computeSwapAmountsInGivenOut(poolId uint64, curSqrtPri if shouldCrossTick { // Runs regular tick crossing logic. curSqrtPrice = s.tickToSqrtPrice(nextInitializedTick) - currentLiquidity = currentLiquidity.Add(osmomath.BigDecFromDec(liquidityNetEntry.LiquidityNet)) + currentLiquidity = currentLiquidity.Add(liquidityNetEntry.LiquidityNet) currentTick = nextInitializedTick } @@ -643,7 +643,7 @@ func (s *KeeperTestSuite) TestSwapOutGivenIn_Tick_Initialization_And_Crossing() if tickToSwapTo < nr1Position.lowerTick { sqrtPriceLowerTickOne := s.tickToSqrtPrice(nr1Position.lowerTick) - amountZeroIn = math.CalcAmount0Delta(osmomath.BigDecFromDec(liquidity), sqrtPriceLowerTickOne, sqrtPriceStart, true).Dec() + amountZeroIn = math.CalcAmount0Delta(liquidity, sqrtPriceLowerTickOne, sqrtPriceStart, true).Dec() sqrtPriceStart = sqrtPriceLowerTickOne @@ -652,7 +652,7 @@ func (s *KeeperTestSuite) TestSwapOutGivenIn_Tick_Initialization_And_Crossing() // This is the total amount necessary to cross the lower tick of narrow position. // Note it is rounded up to ensure that the tick is crossed. - amountZeroIn = math.CalcAmount0Delta(osmomath.BigDecFromDec(liquidity), sqrtPriceTarget, sqrtPriceStart, true).DecRoundUp().Add(amountZeroIn) + amountZeroIn = math.CalcAmount0Delta(liquidity, sqrtPriceTarget, sqrtPriceStart, true).DecRoundUp().Add(amountZeroIn) tokenZeroIn := sdk.NewCoin(pool.GetToken0(), amountZeroIn.Ceil().TruncateInt()) @@ -734,7 +734,7 @@ func (s *KeeperTestSuite) TestSwapOutGivenIn_Tick_Initialization_And_Crossing() if tickToSwapTo >= nr1Position.upperTick { sqrtPriceUpperOne := s.tickToSqrtPrice(nr1Position.upperTick) - amountOneIn = math.CalcAmount1Delta(osmomath.BigDecFromDec(liquidity), sqrtPriceUpperOne, sqrtPriceStart, true).DecRoundUp() + amountOneIn = math.CalcAmount1Delta(liquidity, sqrtPriceUpperOne, sqrtPriceStart, true).DecRoundUp() sqrtPriceStart = sqrtPriceUpperOne @@ -743,7 +743,7 @@ func (s *KeeperTestSuite) TestSwapOutGivenIn_Tick_Initialization_And_Crossing() // This is the total amount necessary to cross the lower tick of narrow position. // Note it is rounded up to ensure that the tick is crossed. - amountOneIn = math.CalcAmount1Delta(osmomath.BigDecFromDec(liquidity), sqrtPriceTarget, sqrtPriceStart, true).DecRoundUp().Add(amountOneIn) + amountOneIn = math.CalcAmount1Delta(liquidity, sqrtPriceTarget, sqrtPriceStart, true).DecRoundUp().Add(amountOneIn) tokenOneIn := sdk.NewCoin(pool.GetToken1(), amountOneIn.Ceil().TruncateInt()) @@ -1201,6 +1201,7 @@ func (s *KeeperTestSuite) TestSwaps_Contiguous_Initialized_TickSpacingOne() { // estimateAmountInFromRounding is a helper to estimate the impact of amountOut rounding on the amountIn and next sqrt price. // This is necessary for correct amount in estimation to pre-fund the swapper account to. It is also required for updating // the "current sqrt price" for the next swap in the sequence as defined by our test configuration. + // TODO: Change type arg of liq estimateAmountInFromRounding := func(isZeroForOne bool, nextSqrtPrice osmomath.BigDec, liq osmomath.BigDec, amountOutDifference osmomath.BigDec) (osmomath.Dec, osmomath.BigDec) { if !liq.IsPositive() { return osmomath.ZeroDec(), nextSqrtPrice @@ -1210,17 +1211,17 @@ func (s *KeeperTestSuite) TestSwaps_Contiguous_Initialized_TickSpacingOne() { // Round down since we want to overestimate the change in sqrt price stemming from the amount out going right-to-left // from the current sqrt price. This overestimated value is then used to calculate amount in charged on the user. // Since amount in is overestimated, this done in favor of the pool. - updatedNextCurSqrtPrice := math.GetNextSqrtPriceFromAmount1OutRoundingDown(nextSqrtPrice, liq, amountOutDifference) + updatedNextCurSqrtPrice := math.GetNextSqrtPriceFromAmount1OutRoundingDown(nextSqrtPrice, liq.Dec(), amountOutDifference) // Round up since we want to overestimate the amount in in favor of the pool. - return math.CalcAmount0Delta(liq, updatedNextCurSqrtPrice, nextSqrtPrice, true).DecRoundUp(), updatedNextCurSqrtPrice + return math.CalcAmount0Delta(liq.Dec(), updatedNextCurSqrtPrice, nextSqrtPrice, true).DecRoundUp(), updatedNextCurSqrtPrice } // Round up since we want to overestimate the change in sqrt price stemming from the amount out going left-to-right // from the current sqrt price. This overestimated value is then used to calculate amount in charged on the user. // Since amount in is overestimated, this is done in favor of the pool. - updatedNextCurSqrtPrice := math.GetNextSqrtPriceFromAmount0OutRoundingUp(nextSqrtPrice, liq, amountOutDifference) + updatedNextCurSqrtPrice := math.GetNextSqrtPriceFromAmount0OutRoundingUp(nextSqrtPrice, liq, amountOutDifference.Dec()) // Round up since we want to overestimate the amount in in favor of the pool. - return math.CalcAmount1Delta(liq, updatedNextCurSqrtPrice, nextSqrtPrice, true).DecRoundUp(), updatedNextCurSqrtPrice + return math.CalcAmount1Delta(liq.Dec(), updatedNextCurSqrtPrice, nextSqrtPrice, true).DecRoundUp(), updatedNextCurSqrtPrice } for name, tc := range testcases { @@ -1277,7 +1278,8 @@ func (s *KeeperTestSuite) TestSwaps_Contiguous_Initialized_TickSpacingOne() { // properly estimating the next swap. This also allows us to precisely calculate by how many tokens in we need // to pre-fund the swapper account. amountOutDifference := amountOutRoundedUp.ToLegacyDec().Sub(amountOut) - amountInFromRounding, updatedNextCurSqrtPrice := estimateAmountInFromRounding(isZeroForOne, nextSqrtPrice, expectedLiquidity, osmomath.BigDecFromDec(amountOutDifference)) + liqBigDec := osmomath.BigDecFromDec(expectedLiquidity) // TODO: Delete + amountInFromRounding, updatedNextCurSqrtPrice := estimateAmountInFromRounding(isZeroForOne, nextSqrtPrice, liqBigDec, osmomath.BigDecFromDec(amountOutDifference)) amountInToPreFund := amountIn.Add(amountInFromRounding) // Perform the swap in the desired direction. @@ -1293,7 +1295,7 @@ func (s *KeeperTestSuite) TestSwaps_Contiguous_Initialized_TickSpacingOne() { // Validate that current tick and current liquidity are as expected. s.assertPoolTickEquals(poolId, expectedSwapEndTick) - s.assertPoolLiquidityEquals(poolId, expectedLiquidity.Dec()) + s.assertPoolLiquidityEquals(poolId, expectedLiquidity) // Update the current sqrt price and tick for next swap. curSqrtPrice = updatedNextCurSqrtPrice diff --git a/x/concentrated-liquidity/swapstrategy/one_for_zero.go b/x/concentrated-liquidity/swapstrategy/one_for_zero.go index 53bebd04df6..a4d24e5c204 100644 --- a/x/concentrated-liquidity/swapstrategy/one_for_zero.go +++ b/x/concentrated-liquidity/swapstrategy/one_for_zero.go @@ -60,16 +60,12 @@ func (s oneForZeroStrategy) GetSqrtTargetPrice(nextTickSqrtPrice osmomath.BigDec // OneForZero details: // - oneForZeroStrategy assumes moving to the right of the current square root price. func (s oneForZeroStrategy) ComputeSwapWithinBucketOutGivenIn(sqrtPriceCurrent, sqrtPriceTarget osmomath.BigDec, liquidity, amountOneInRemaining osmomath.Dec) (osmomath.BigDec, osmomath.Dec, osmomath.Dec, osmomath.Dec) { - // TODO: Change to Dec - liquidityBigDec := osmomath.BigDecFromDec(liquidity) - amountOneInRemainingBigDec := osmomath.BigDecFromDec(amountOneInRemaining) - // Estimate the amount of token one needed until the target sqrt price is reached. - amountOneIn := math.CalcAmount1Delta(liquidityBigDec, sqrtPriceTarget, sqrtPriceCurrent, true) + amountOneIn := math.CalcAmount1Delta(liquidity, sqrtPriceTarget, sqrtPriceCurrent, true) // Calculate sqrtPriceNext on the amount of token remaining after spread reward. - // TODO: Use MulTruncateDec - amountOneInRemainingLessSpreadReward := amountOneInRemainingBigDec.MulTruncate(oneBigDec.Sub(osmomath.BigDecFromDec(s.spreadFactor))) + oneMinusTakerFee := oneDec.Sub(s.spreadFactor) + amountOneInRemainingLessSpreadReward := osmomath.NewBigDecFromDecMulDec(amountOneInRemaining, oneMinusTakerFee) var sqrtPriceNext osmomath.BigDec // If have more of the amount remaining after spread reward than estimated until target, @@ -78,7 +74,7 @@ func (s oneForZeroStrategy) ComputeSwapWithinBucketOutGivenIn(sqrtPriceCurrent, sqrtPriceNext = sqrtPriceTarget } else { // Otherwise, compute the next sqrt price based on the amount remaining after spread reward. - sqrtPriceNext = math.GetNextSqrtPriceFromAmount1InRoundingDown(sqrtPriceCurrent, liquidityBigDec, amountOneInRemainingLessSpreadReward) + sqrtPriceNext = math.GetNextSqrtPriceFromAmount1InRoundingDown(sqrtPriceCurrent, liquidity, amountOneInRemainingLessSpreadReward) } hasReachedTarget := sqrtPriceTarget.Equal(sqrtPriceNext) @@ -87,11 +83,11 @@ func (s oneForZeroStrategy) ComputeSwapWithinBucketOutGivenIn(sqrtPriceCurrent, // to complete the swap step. This implies that some of the amount remaining after spread reward is left over after the // current swap step. if !hasReachedTarget { - amountOneIn = math.CalcAmount1Delta(liquidityBigDec, sqrtPriceNext, sqrtPriceCurrent, true) // N.B.: if this is false, causes infinite loop + amountOneIn = math.CalcAmount1Delta(liquidity, sqrtPriceNext, sqrtPriceCurrent, true) // N.B.: if this is false, causes infinite loop } // Calculate the amount of the other token given the sqrt price range. - amountZeroOut := math.CalcAmount0Delta(liquidityBigDec, sqrtPriceNext, sqrtPriceCurrent, false) + amountZeroOut := math.CalcAmount0Delta(liquidity, sqrtPriceNext, sqrtPriceCurrent, false) // Round up to charge user more in pool's favor. amountInDecFinal := amountOneIn.DecRoundUp() @@ -130,7 +126,7 @@ func (s oneForZeroStrategy) ComputeSwapWithinBucketInGivenOut(sqrtPriceCurrent, // Estimate the amount of token zero needed until the target sqrt price is reached. // N.B.: contrary to out given in, we do not round up because we do not want to exceed the initial amount out at the end. - amountZeroOut := math.CalcAmount0Delta(liquidityBigDec, sqrtPriceTarget, sqrtPriceCurrent, false) + amountZeroOut := math.CalcAmount0Delta(liquidity, sqrtPriceTarget, sqrtPriceCurrent, false) // Calculate sqrtPriceNext on the amount of token remaining. Note that the // spread reward is not charged as amountRemaining is amountOut, and we only charge spread reward on @@ -142,7 +138,7 @@ func (s oneForZeroStrategy) ComputeSwapWithinBucketInGivenOut(sqrtPriceCurrent, sqrtPriceNext = sqrtPriceTarget } else { // Otherwise, compute the next sqrt price based on the amount remaining after spread reward. - sqrtPriceNext = math.GetNextSqrtPriceFromAmount0OutRoundingUp(sqrtPriceCurrent, liquidityBigDec, amountZeroRemainingOutBigDec) + sqrtPriceNext = math.GetNextSqrtPriceFromAmount0OutRoundingUp(sqrtPriceCurrent, liquidityBigDec, amountZeroRemainingOut) } hasReachedTarget := sqrtPriceTarget.Equal(sqrtPriceNext) @@ -152,11 +148,11 @@ func (s oneForZeroStrategy) ComputeSwapWithinBucketInGivenOut(sqrtPriceCurrent, // current swap step. if !hasReachedTarget { // N.B.: contrary to out given in, we do not round up because we do not want to exceed the initial amount out at the end. - amountZeroOut = math.CalcAmount0Delta(liquidityBigDec, sqrtPriceNext, sqrtPriceCurrent, false) + amountZeroOut = math.CalcAmount0Delta(liquidity, sqrtPriceNext, sqrtPriceCurrent, false) } // Calculate the amount of the other token given the sqrt price range. - amountOneIn := math.CalcAmount1Delta(liquidityBigDec, sqrtPriceNext, sqrtPriceCurrent, true) + amountOneIn := math.CalcAmount1Delta(liquidity, sqrtPriceNext, sqrtPriceCurrent, true) // Round up to charge user more in pool's favor. amountOneInFinal := amountOneIn.DecRoundUp() diff --git a/x/concentrated-liquidity/swapstrategy/spread_rewards.go b/x/concentrated-liquidity/swapstrategy/spread_rewards.go index a99e31d894a..47d8b3b54ba 100644 --- a/x/concentrated-liquidity/swapstrategy/spread_rewards.go +++ b/x/concentrated-liquidity/swapstrategy/spread_rewards.go @@ -23,17 +23,14 @@ import ( // If spread factor is negative, it panics. // If spread factor is 0, returns 0. Otherwise, computes and returns the spread factor charge per step. func computeSpreadRewardChargePerSwapStepOutGivenIn(hasReachedTarget bool, amountIn, amountSpecifiedRemaining, spreadFactor osmomath.Dec) osmomath.Dec { - spreadRewardChargeTotal := osmomath.ZeroDec() - - if spreadFactor.IsNegative() { + if spreadFactor.IsZero() { + return osmomath.ZeroDec() + } else if spreadFactor.IsNegative() { // This should never happen but is added as a defense-in-depth measure. panic(fmt.Errorf("spread factor must be non-negative, was (%s)", spreadFactor)) } - if spreadFactor.IsZero() { - return spreadRewardChargeTotal - } - + var spreadRewardChargeTotal osmomath.Dec if hasReachedTarget { // This branch implies two options: // 1) either sqrtPriceNextTick is reached @@ -61,6 +58,7 @@ func computeSpreadRewardChargePerSwapStepOutGivenIn(hasReachedTarget bool, amoun // Computes amountIn * spreadFactor / (1 - spreadFactor) where math operations round up // at precision end. This is necessary to ensure that the spread factor charge is always // rounded in favor of the pool. +// TODO: Change this fn to take in 1 - spreadFactor as it should already have been computed. func computeSpreadRewardChargeFromAmountIn(amountIn osmomath.Dec, spreadFactor osmomath.Dec) osmomath.Dec { return amountIn.MulRoundUp(spreadFactor).QuoRoundupMut(osmomath.OneDec().SubMut(spreadFactor)) } diff --git a/x/concentrated-liquidity/swapstrategy/swap_strategy.go b/x/concentrated-liquidity/swapstrategy/swap_strategy.go index b691816f052..833993bee63 100644 --- a/x/concentrated-liquidity/swapstrategy/swap_strategy.go +++ b/x/concentrated-liquidity/swapstrategy/swap_strategy.go @@ -90,7 +90,7 @@ type SwapStrategy interface { } var ( - oneBigDec = osmomath.OneBigDec() + oneDec = osmomath.OneDec() ) // New returns a swap strategy based on the provided zeroForOne parameter diff --git a/x/concentrated-liquidity/swapstrategy/zero_for_one.go b/x/concentrated-liquidity/swapstrategy/zero_for_one.go index 1102d12ef51..3029b5e1978 100644 --- a/x/concentrated-liquidity/swapstrategy/zero_for_one.go +++ b/x/concentrated-liquidity/swapstrategy/zero_for_one.go @@ -61,13 +61,13 @@ func (s zeroForOneStrategy) GetSqrtTargetPrice(nextTickSqrtPrice osmomath.BigDec // - zeroForOneStrategy assumes moving to the left of the current square root price. func (s zeroForOneStrategy) ComputeSwapWithinBucketOutGivenIn(sqrtPriceCurrent, sqrtPriceTarget osmomath.BigDec, liquidity, amountZeroInRemaining osmomath.Dec) (osmomath.BigDec, osmomath.Dec, osmomath.Dec, osmomath.Dec) { liquidityBigDec := osmomath.BigDecFromDec(liquidity) - amountZeroInRemainingBigDec := osmomath.BigDecFromDec(amountZeroInRemaining) // Estimate the amount of token zero needed until the target sqrt price is reached. - amountZeroIn := math.CalcAmount0Delta(liquidityBigDec, sqrtPriceTarget, sqrtPriceCurrent, true) // N.B.: if this is false, causes infinite loop + amountZeroIn := math.CalcAmount0Delta(liquidity, sqrtPriceTarget, sqrtPriceCurrent, true) // N.B.: if this is false, causes infinite loop // Calculate sqrtPriceNext on the amount of token remaining after spread reward. - amountZeroInRemainingLessSpreadReward := amountZeroInRemainingBigDec.Mul(oneBigDec.Sub(osmomath.BigDecFromDec(s.spreadFactor))) + oneMinusTakerFee := oneDec.Sub(s.spreadFactor) + amountZeroInRemainingLessSpreadReward := osmomath.NewBigDecFromDecMulDec(amountZeroInRemaining, oneMinusTakerFee) var sqrtPriceNext osmomath.BigDec // If have more of the amount remaining after spread reward than estimated until target, @@ -85,11 +85,11 @@ func (s zeroForOneStrategy) ComputeSwapWithinBucketOutGivenIn(sqrtPriceCurrent, // to complete the swap step. This implies that some of the amount remaining after spread reward is left over after the // current swap step. if !hasReachedTarget { - amountZeroIn = math.CalcAmount0Delta(liquidityBigDec, sqrtPriceNext, sqrtPriceCurrent, true) // N.B.: if this is false, causes infinite loop + amountZeroIn = math.CalcAmount0Delta(liquidity, sqrtPriceNext, sqrtPriceCurrent, true) // N.B.: if this is false, causes infinite loop } // Calculate the amount of the other token given the sqrt price range. - amountOneOut := math.CalcAmount1Delta(liquidityBigDec, sqrtPriceNext, sqrtPriceCurrent, false) + amountOneOut := math.CalcAmount1Delta(liquidity, sqrtPriceNext, sqrtPriceCurrent, false) // Round up to charge user more in pool's favor. amountZeroInFinal := amountZeroIn.DecRoundUp() @@ -123,11 +123,10 @@ func (s zeroForOneStrategy) ComputeSwapWithinBucketOutGivenIn(sqrtPriceCurrent, // ZeroForOne details: // - zeroForOneStrategy assumes moving to the left of the current square root price. func (s zeroForOneStrategy) ComputeSwapWithinBucketInGivenOut(sqrtPriceCurrent, sqrtPriceTarget osmomath.BigDec, liquidity, amountOneRemainingOut osmomath.Dec) (osmomath.BigDec, osmomath.Dec, osmomath.Dec, osmomath.Dec) { - liquidityBigDec := osmomath.BigDecFromDec(liquidity) amountOneRemainingOutBigDec := osmomath.BigDecFromDec(amountOneRemainingOut) // Estimate the amount of token one needed until the target sqrt price is reached. - amountOneOut := math.CalcAmount1Delta(liquidityBigDec, sqrtPriceTarget, sqrtPriceCurrent, false) + amountOneOut := math.CalcAmount1Delta(liquidity, sqrtPriceTarget, sqrtPriceCurrent, false) // Calculate sqrtPriceNext on the amount of token remaining. Note that the // spread reward is not charged as amountRemaining is amountOut, and we only charge spread reward on @@ -139,7 +138,7 @@ func (s zeroForOneStrategy) ComputeSwapWithinBucketInGivenOut(sqrtPriceCurrent, sqrtPriceNext = sqrtPriceTarget } else { // Otherwise, compute the next sqrt price based on the amount remaining after spread reward. - sqrtPriceNext = math.GetNextSqrtPriceFromAmount1OutRoundingDown(sqrtPriceCurrent, liquidityBigDec, amountOneRemainingOutBigDec) + sqrtPriceNext = math.GetNextSqrtPriceFromAmount1OutRoundingDown(sqrtPriceCurrent, liquidity, amountOneRemainingOutBigDec) } hasReachedTarget := sqrtPriceTarget.Equal(sqrtPriceNext) @@ -148,11 +147,11 @@ func (s zeroForOneStrategy) ComputeSwapWithinBucketInGivenOut(sqrtPriceCurrent, // to complete the swap step. This implies that some of the amount remaining after spread reward is left over after the // current swap step. if !hasReachedTarget { - amountOneOut = math.CalcAmount1Delta(liquidityBigDec, sqrtPriceNext, sqrtPriceCurrent, false) + amountOneOut = math.CalcAmount1Delta(liquidity, sqrtPriceNext, sqrtPriceCurrent, false) } // Calculate the amount of the other token given the sqrt price range. - amountZeroIn := math.CalcAmount0Delta(liquidityBigDec, sqrtPriceNext, sqrtPriceCurrent, true) + amountZeroIn := math.CalcAmount0Delta(liquidity, sqrtPriceNext, sqrtPriceCurrent, true) // Round up to charge user more in pool's favor. amountZeroInFinal := amountZeroIn.DecRoundUp()