diff --git a/CHANGELOG.md b/CHANGELOG.md index 320649ce816..96fcc7a3e37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features * [#6427](https://github.com/osmosis-labs/osmosis/pull/6427) sdk.Coins Mul and Quo helpers in osmoutils +* [#6437](https://github.com/osmosis-labs/osmosis/pull/6437) mutative version for QuoRoundUp. Replace some non-mutative calls with mutative for better performance. + * [#6416](https://github.com/osmosis-labs/osmosis/pull/6416) feat[CL]: add num initialized ticks query ### Bug Fixes diff --git a/osmomath/decimal.go b/osmomath/decimal.go index 72dcb42c55b..4141276597b 100644 --- a/osmomath/decimal.go +++ b/osmomath/decimal.go @@ -36,13 +36,14 @@ const ( ) var ( - precisionReuse = new(big.Int).Exp(big.NewInt(10), big.NewInt(PrecisionBigDec), nil) - precisionReuseSDK = new(big.Int).Exp(big.NewInt(10), big.NewInt(PrecisionDec), nil) - fivePrecision = new(big.Int).Quo(precisionReuse, big.NewInt(2)) - precisionMultipliers []*big.Int - zeroInt = big.NewInt(0) - oneInt = big.NewInt(1) - tenInt = big.NewInt(10) + precisionReuse = new(big.Int).Exp(big.NewInt(10), big.NewInt(PrecisionBigDec), nil) + squaredPrecisionReuse = new(big.Int).Mul(precisionReuse, precisionReuse) + precisionReuseSDK = new(big.Int).Exp(big.NewInt(10), big.NewInt(PrecisionDec), nil) + fivePrecision = new(big.Int).Quo(precisionReuse, big.NewInt(2)) + precisionMultipliers []*big.Int + zeroInt = big.NewInt(0) + oneInt = big.NewInt(1) + tenInt = big.NewInt(10) // log_2(e) // From: https://www.wolframalpha.com/input?i=log_2%28e%29+with+37+digits @@ -401,6 +402,20 @@ func (d BigDec) QuoRoundUp(d2 BigDec) BigDec { 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) + + chopPrecisionAndRoundUpMut(d.i, precisionReuse) + + if d.i.BitLen() > maxDecBitLen { + panic("Int overflow") + } + return BigDec{d.i} +} + // quotient func (d BigDec) QuoInt(i BigInt) BigDec { mul := new(big.Int).Quo(d.i, i.i) @@ -648,36 +663,44 @@ func chopPrecisionAndRound(d *big.Int) *big.Int { } // chopPrecisionAndRoundUpBigDec removes a Precision amount of rightmost digits and rounds up. +// Non-mutative. func chopPrecisionAndRoundUpBigDec(d *big.Int) *big.Int { - return chopPrecisionAndRoundUp(d, precisionReuse) + // make copy + copy := new(big.Int).Set(d) + return chopPrecisionAndRoundUpMut(copy, precisionReuse) } // chopPrecisionAndRoundUpDec removes sdk.Precision amount of rightmost digits and rounds up. +// Non-mutative. func chopPrecisionAndRoundUpDec(d *big.Int) *big.Int { - return chopPrecisionAndRoundUp(d, precisionReuseSDK) + copy := new(big.Int).Set(d) + return chopPrecisionAndRoundUpMut(copy, precisionReuseSDK) } // chopPrecisionAndRoundUp removes a Precision amount of rightmost digits and rounds up. -func chopPrecisionAndRoundUp(d *big.Int, precisionReuse *big.Int) *big.Int { +// Mutates input d. +// Mutations occur: +// - By calling chopPrecisionAndTruncateMut. +// - Using input d directly in QuoRem. +func chopPrecisionAndRoundUpMut(d *big.Int, precisionReuse *big.Int) *big.Int { // remove the negative and add it back when returning if d.Sign() == -1 { // make d positive, compute chopped value, and then un-mutate d d = d.Neg(d) // truncate since d is negative... - d = chopPrecisionAndTruncate(d) + d = chopPrecisionAndTruncateMut(d) d = d.Neg(d) return d } // get the truncated quotient and remainder - quo, rem := d, big.NewInt(0) - quo, rem = quo.QuoRem(d, precisionReuse, rem) + _, rem := d.QuoRem(d, precisionReuse, big.NewInt(0)) if rem.Sign() == 0 { // remainder is zero - return quo + return d } - return quo.Add(quo, oneInt) + return d.Add(d, oneInt) } func chopPrecisionAndRoundNonMutative(d *big.Int) *big.Int { @@ -705,6 +728,12 @@ func chopPrecisionAndTruncate(d *big.Int) *big.Int { return new(big.Int).Quo(d, precisionReuse) } +// chopPrecisionAndTruncate is similar to chopPrecisionAndRound, +// but always rounds down. It mutates the input. +func chopPrecisionAndTruncateMut(d *big.Int) *big.Int { + return d.Quo(d, precisionReuse) +} + // TruncateInt64 truncates the decimals from the number and returns an int64 func (d BigDec) TruncateInt64() int64 { chopped := chopPrecisionAndTruncate(d.i) diff --git a/osmomath/decimal_test.go b/osmomath/decimal_test.go index 84e5ec05054..0ac1455f3e0 100644 --- a/osmomath/decimal_test.go +++ b/osmomath/decimal_test.go @@ -1480,3 +1480,61 @@ func (s *decimalTestSuite) TestPower() { }) } } + +func (s *decimalTestSuite) TestQuoRoundUp_MutativeAndNonMutative() { + tests := []struct { + d1, d2, expQuoRoundUpMut osmomath.BigDec + }{ + {osmomath.NewBigDec(0), osmomath.NewBigDec(0), osmomath.NewBigDec(0)}, + {osmomath.NewBigDec(1), osmomath.NewBigDec(0), osmomath.NewBigDec(0)}, + {osmomath.NewBigDec(0), osmomath.NewBigDec(1), osmomath.NewBigDec(0)}, + {osmomath.NewBigDec(0), osmomath.NewBigDec(-1), osmomath.NewBigDec(0)}, + {osmomath.NewBigDec(-1), osmomath.NewBigDec(0), osmomath.NewBigDec(0)}, + + {osmomath.NewBigDec(1), osmomath.NewBigDec(1), osmomath.NewBigDec(1)}, + {osmomath.NewBigDec(-1), osmomath.NewBigDec(-1), osmomath.NewBigDec(1)}, + {osmomath.NewBigDec(1), osmomath.NewBigDec(-1), osmomath.NewBigDec(-1)}, + {osmomath.NewBigDec(-1), osmomath.NewBigDec(1), osmomath.NewBigDec(-1)}, + + { + osmomath.NewBigDec(3), osmomath.NewBigDec(7), osmomath.MustNewBigDecFromStr("0.428571428571428571428571428571428572"), + }, + { + osmomath.NewBigDec(2), osmomath.NewBigDec(4), osmomath.NewBigDecWithPrec(5, 1), + }, + + {osmomath.NewBigDec(100), osmomath.NewBigDec(100), osmomath.NewBigDec(1)}, + + { + osmomath.NewBigDecWithPrec(15, 1), osmomath.NewBigDecWithPrec(15, 1), osmomath.NewBigDec(1), + }, + { + osmomath.NewBigDecWithPrec(3333, 4), osmomath.NewBigDecWithPrec(333, 4), osmomath.MustNewBigDecFromStr("10.009009009009009009009009009009009010"), + }, + } + + for tcIndex, tc := range tests { + tc := tc + + if tc.d2.IsZero() { // panic for divide by zero + s.Require().Panics(func() { tc.d1.QuoRoundUpMut(tc.d2) }) + } else { + + copy := tc.d1.Clone() + + nonMutResult := copy.QuoRoundUp(tc.d2) + + // Return is as expected + s.Require().Equal(tc.expQuoRoundUpMut, nonMutResult, "exp %v, res %v, tc %d", tc.expQuoRoundUpMut.String(), tc.d1.String(), tcIndex) + + // Receiver is not mutated + s.Require().Equal(tc.d1, copy, "exp %v, res %v, tc %d", tc.expQuoRoundUpMut.String(), tc.d1.String(), tcIndex) + + // Receiver is mutated. + tc.d1.QuoRoundUpMut(tc.d2) + + // Make sure d1 equals to expected + s.Require().True(tc.expQuoRoundUpMut.Equal(tc.d1), "exp %v, res %v, tc %d", tc.expQuoRoundUpMut.String(), tc.d1.String(), tcIndex) + } + } +} diff --git a/x/concentrated-liquidity/math/math.go b/x/concentrated-liquidity/math/math.go index b1a0930411d..3d1ad633883 100644 --- a/x/concentrated-liquidity/math/math.go +++ b/x/concentrated-liquidity/math/math.go @@ -130,7 +130,7 @@ func GetNextSqrtPriceFromAmount0InRoundingUp(sqrtPriceCurrent, liquidity, amount // denominator = product + liquidity denominator := product denominator.AddMut(liquidity) - return liquidity.Mul(sqrtPriceCurrent).QuoRoundUp(denominator) + return liquidity.Mul(sqrtPriceCurrent).QuoRoundUpMut(denominator) } // GetNextSqrtPriceFromAmount0OutRoundingUp utilizes sqrtPriceCurrent, liquidity, and amount of denom0 that still needs @@ -148,7 +148,7 @@ func GetNextSqrtPriceFromAmount0OutRoundingUp(sqrtPriceCurrent, liquidity, amoun denominator := liquidity.Sub(product) // mul round up numerator to make the final result larger // quo round up to make the final result larger - return liquidity.MulRoundUp(sqrtPriceCurrent).QuoRoundUp(denominator) + return liquidity.MulRoundUp(sqrtPriceCurrent).QuoRoundUpMut(denominator) } // GetNextSqrtPriceFromAmount1InRoundingDown utilizes the current sqrtPriceCurrent, liquidity, and amount of denom1 that still needs @@ -166,7 +166,7 @@ func GetNextSqrtPriceFromAmount1InRoundingDown(sqrtPriceCurrent, liquidity, amou // 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)) + return sqrtPriceCurrent.Sub(amountOneRemainingOut.QuoRoundUpMut(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/gamm/pool-models/stableswap/amm.go b/x/gamm/pool-models/stableswap/amm.go index 641a7e334a2..b598a7b6d14 100644 --- a/x/gamm/pool-models/stableswap/amm.go +++ b/x/gamm/pool-models/stableswap/amm.go @@ -223,7 +223,7 @@ func (p *Pool) calcInAmtGivenOut(tokenOut sdk.Coin, tokenInDenom string, spreadF // We invert that negative here. cfmmIn = cfmmIn.Neg() // divide by (1 - spread factor) to force a corresponding increase in input asset - inAmt := cfmmIn.QuoRoundUp(oneMinus(spreadFactor)) + inAmt := cfmmIn.QuoRoundUpMut(oneMinus(spreadFactor)) inCoinAmt := p.getDescaledPoolAmt(tokenInDenom, inAmt) return inCoinAmt, nil }