From 4caa4e06dee288ce6c4e3a682e1efba42711fe61 Mon Sep 17 00:00:00 2001 From: it4rb Date: Mon, 25 Mar 2024 17:27:35 +0700 Subject: [PATCH] optimize logic and reduce alloc (#2) * improve GetOutputAmount, MulDiv, MulDivRoundingUp * reduce alloc * add comment * add RemainingAmountIn --- entities/pool.go | 69 ++++++++++++------ utils/full_math.go | 131 ++++++++++++++++++++++++++++++++-- utils/full_math_test.go | 97 +++++++++++++++++++++++++ utils/int_types.go | 7 ++ utils/most_significant_bit.go | 2 +- utils/sqrtprice_math.go | 102 +++++++++++++++----------- utils/sqrtprice_math_test.go | 40 ++++++----- utils/swap_math.go | 72 +++++++++---------- utils/swap_math_test.go | 5 +- utils/tick_math.go | 24 ++++--- utils/tick_math_test.go | 13 ++-- 11 files changed, 423 insertions(+), 139 deletions(-) diff --git a/entities/pool.go b/entities/pool.go index 98ed498..eaff2c8 100644 --- a/entities/pool.go +++ b/entities/pool.go @@ -21,13 +21,13 @@ var ( ) type StepComputations struct { - sqrtPriceStartX96 *utils.Uint160 + sqrtPriceStartX96 utils.Uint160 tickNext int initialized bool - sqrtPriceNextX96 *utils.Uint160 - amountIn *utils.Uint256 - amountOut *utils.Uint256 - feeAmount *utils.Uint256 + sqrtPriceNextX96 utils.Uint160 + amountIn utils.Uint256 + amountOut utils.Uint256 + feeAmount utils.Uint256 } // Represents a V3 pool @@ -60,6 +60,15 @@ type GetAmountResult struct { CrossInitTickLoops int } +type GetAmountResultV2 struct { + ReturnedAmount *utils.Int256 + RemainingAmountIn *utils.Int256 + SqrtRatioX96 *utils.Uint160 + Liquidity *utils.Uint128 + CurrentTick int + CrossInitTickLoops int +} + func GetAddress(tokenA, tokenB *entities.Token, fee constants.FeeAmount, initCodeHashManualOverride string) (common.Address, error) { return utils.ComputePoolAddress(constants.FactoryAddress, tokenA, tokenB, fee, initCodeHashManualOverride) } @@ -90,16 +99,17 @@ func NewPoolV2(tokenA, tokenB *entities.Token, fee constants.FeeAmount, sqrtRati return nil, ErrFeeTooHigh } - tickCurrentSqrtRatioX96, err := utils.GetSqrtRatioAtTickV2(tickCurrent) + var tickCurrentSqrtRatioX96, nextTickSqrtRatioX96 utils.Uint160 + err := utils.GetSqrtRatioAtTickV2(tickCurrent, &tickCurrentSqrtRatioX96) if err != nil { return nil, err } - nextTickSqrtRatioX96, err := utils.GetSqrtRatioAtTickV2(tickCurrent + 1) + err = utils.GetSqrtRatioAtTickV2(tickCurrent+1, &nextTickSqrtRatioX96) if err != nil { return nil, err } - if sqrtRatioX96.Cmp(tickCurrentSqrtRatioX96) < 0 || sqrtRatioX96.Cmp(nextTickSqrtRatioX96) > 0 { + if sqrtRatioX96.Cmp(&tickCurrentSqrtRatioX96) < 0 || sqrtRatioX96.Cmp(&nextTickSqrtRatioX96) > 0 { return nil, ErrInvalidSqrtRatioX96 } token0 := tokenA @@ -216,6 +226,21 @@ func (p *Pool) GetOutputAmount(inputAmount *entities.CurrencyAmount, sqrtPriceLi }, nil } +func (p *Pool) GetOutputAmountV2(inputAmount *utils.Int256, zeroForOne bool, sqrtPriceLimitX96 *utils.Uint160) (*GetAmountResultV2, error) { + swapResult, err := p.swap(zeroForOne, inputAmount, sqrtPriceLimitX96) + if err != nil { + return nil, err + } + return &GetAmountResultV2{ + ReturnedAmount: new(utils.Int256).Neg(swapResult.amountCalculated), + RemainingAmountIn: new(utils.Int256).Set(swapResult.remainingAmountIn), + SqrtRatioX96: swapResult.sqrtRatioX96, + Liquidity: swapResult.liquidity, + CurrentTick: swapResult.currentTick, + CrossInitTickLoops: swapResult.crossInitTickLoops, + }, nil +} + /** * Given a desired output amount of a token, return the computed input amount and a pool with state updated after the trade * @param outputAmount the output amount for which to quote the input amount @@ -318,7 +343,7 @@ func (p *Pool) swap(zeroForOne bool, amountSpecified *utils.Int256, sqrtPriceLim // start swap while loop for !state.amountSpecifiedRemaining.IsZero() && state.sqrtPriceX96.Cmp(sqrtPriceLimitX96) != 0 { var step StepComputations - step.sqrtPriceStartX96 = state.sqrtPriceX96 + step.sqrtPriceStartX96.Set(state.sqrtPriceX96) // because each iteration of the while loop rounds, we can't optimize this code (relative to the smart contract) // by simply traversing to the next available tick, we instead need to exactly replicate @@ -334,32 +359,35 @@ func (p *Pool) swap(zeroForOne bool, amountSpecified *utils.Int256, sqrtPriceLim step.tickNext = utils.MaxTick } - step.sqrtPriceNextX96, err = utils.GetSqrtRatioAtTickV2(step.tickNext) + err = utils.GetSqrtRatioAtTickV2(step.tickNext, &step.sqrtPriceNextX96) if err != nil { return nil, err } - var targetValue *utils.Uint160 + var targetValue utils.Uint160 if zeroForOne { if step.sqrtPriceNextX96.Cmp(sqrtPriceLimitX96) < 0 { - targetValue = sqrtPriceLimitX96 + targetValue.Set(sqrtPriceLimitX96) } else { - targetValue = step.sqrtPriceNextX96 + targetValue.Set(&step.sqrtPriceNextX96) } } else { if step.sqrtPriceNextX96.Cmp(sqrtPriceLimitX96) > 0 { - targetValue = sqrtPriceLimitX96 + targetValue.Set(sqrtPriceLimitX96) } else { - targetValue = step.sqrtPriceNextX96 + targetValue.Set(&step.sqrtPriceNextX96) } } - state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount, err = utils.ComputeSwapStep(state.sqrtPriceX96, targetValue, state.liquidity, state.amountSpecifiedRemaining, p.Fee) + var nxtSqrtPriceX96 utils.Uint160 + err = utils.ComputeSwapStep(state.sqrtPriceX96, &targetValue, state.liquidity, state.amountSpecifiedRemaining, p.Fee, + &nxtSqrtPriceX96, &step.amountIn, &step.amountOut, &step.feeAmount) if err != nil { return nil, err } + state.sqrtPriceX96.Set(&nxtSqrtPriceX96) var amountInPlusFee utils.Uint256 - amountInPlusFee.Add(step.amountIn, step.feeAmount) + amountInPlusFee.Add(&step.amountIn, &step.feeAmount) var amountInPlusFeeSigned utils.Int256 err = utils.ToInt256(&amountInPlusFee, &amountInPlusFeeSigned) @@ -368,7 +396,7 @@ func (p *Pool) swap(zeroForOne bool, amountSpecified *utils.Int256, sqrtPriceLim } var amountOutSigned utils.Int256 - err = utils.ToInt256(step.amountOut, &amountOutSigned) + err = utils.ToInt256(&step.amountOut, &amountOutSigned) if err != nil { return nil, err } @@ -382,7 +410,7 @@ func (p *Pool) swap(zeroForOne bool, amountSpecified *utils.Int256, sqrtPriceLim } // TODO - if state.sqrtPriceX96.Cmp(step.sqrtPriceNextX96) == 0 { + if state.sqrtPriceX96.Cmp(&step.sqrtPriceNextX96) == 0 { // if the tick is initialized, run the tick transition if step.initialized { tick, err := p.TickDataProvider.GetTick(step.tickNext) @@ -406,7 +434,7 @@ func (p *Pool) swap(zeroForOne bool, amountSpecified *utils.Int256, sqrtPriceLim state.tick = step.tickNext } - } else if state.sqrtPriceX96.Cmp(step.sqrtPriceStartX96) != 0 { + } else if state.sqrtPriceX96.Cmp(&step.sqrtPriceStartX96) != 0 { // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved state.tick, err = utils.GetTickAtSqrtRatioV2(state.sqrtPriceX96) if err != nil { @@ -414,6 +442,7 @@ func (p *Pool) swap(zeroForOne bool, amountSpecified *utils.Int256, sqrtPriceLim } } } + return &SwapResult{ amountCalculated: state.amountCalculated, sqrtRatioX96: state.sqrtPriceX96, diff --git a/utils/full_math.go b/utils/full_math.go index 8d4584b..b8da971 100644 --- a/utils/full_math.go +++ b/utils/full_math.go @@ -30,6 +30,130 @@ func MulDivRoundingUp(a, b, denominator *uint256.Int) (*uint256.Int, error) { return resultU, nil } +func MulDivRoundingUpV2(a, b, denominator, result *uint256.Int) error { + var remainder Uint256 + err := MulDivV2(a, b, denominator, result, &remainder) + if err != nil { + return err + } + + if !remainder.IsZero() { + if result.Cmp(MaxUint256) == 0 { + return ErrInvariant + } + result.AddUint64(result, 1) + } + return nil +} + +// result=floor(a×b÷denominator), remainder=a×b%denominator +// (pass remainder=nil if not required) +// (the main usage for `remainder` is to be used in `MulDivRoundingUpV2` to determine if we need to round up, so it won't have to call MulMod again) +func MulDivV2(a, b, denominator, result, remainder *uint256.Int) error { + // https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/FullMath.sol + // 512-bit multiply [prod1 prod0] = a * b + // Compute the product mod 2**256 and mod 2**256 - 1 + // then use the Chinese Remainder Theorem to reconstruct + // the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2**256 + prod0 + var prod0 Uint256 // Least significant 256 bits of the product + var prod1 Uint256 // Most significant 256 bits of the product + + var denominatorTmp Uint256 // temp var (need to modify denominator along the way) + denominatorTmp.Set(denominator) + + var mm Uint256 + mm.MulMod(a, b, MaxUint256) + prod0.Mul(a, b) + prod1.Sub(&mm, &prod0) + if mm.Cmp(&prod0) < 0 { + prod1.SubUint64(&prod1, 1) + } + + // Handle non-overflow cases, 256 by 256 division + if prod1.IsZero() { + if denominatorTmp.IsZero() { + return ErrInvariant + } + + if remainder != nil { + // if the caller request then calculate remainder + remainder.MulMod(a, b, &denominatorTmp) + } + result.Div(&prod0, &denominatorTmp) + return nil + } + + // Make sure the result is less than 2**256. + // Also prevents denominator == 0 + if denominatorTmp.Cmp(&prod1) <= 0 { + return ErrInvariant + } + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0] + // Compute remainder using mulmod + if remainder == nil { + // the caller doesn't request but we need it so use a temporary variable here + var remainderTmp Uint256 + remainder = &remainderTmp + } + remainder.MulMod(a, b, &denominatorTmp) + // Subtract 256 bit number from 512 bit number + if remainder.Cmp(&prod0) > 0 { + prod1.SubUint64(&prod1, 1) + } + prod0.Sub(&prod0, remainder) + + // Factor powers of two out of denominator + // Compute largest power of two divisor of denominator. + // Always >= 1. + var twos, tmp, tmp1, zero, two, three Uint256 + twos.And(tmp.Neg(&denominatorTmp), &denominatorTmp) + // Divide denominator by power of two + denominatorTmp.Div(&denominatorTmp, &twos) + + // Divide [prod1 prod0] by the factors of two + prod0.Div(&prod0, &twos) + // Shift in bits from prod1 into prod0. For this we need + // to flip `twos` such that it is 2**256 / twos. + // If twos is zero, then it becomes one + zero.Clear() + twos.AddUint64(tmp.Div(tmp1.Sub(&zero, &twos), &twos), 1) + prod0.Or(&prod0, tmp.Mul(&prod1, &twos)) + + // Invert denominator mod 2**256 + // Now that denominator is an odd number, it has an inverse + // modulo 2**256 such that denominator * inv = 1 mod 2**256. + // Compute the inverse by starting with a seed that is correct + // correct for four bits. That is, denominator * inv = 1 mod 2**4 + var inv Uint256 + two.SetUint64(2) + three.SetUint64(3) + inv.Xor(tmp.Mul(&denominatorTmp, &three), &two) + // Now use Newton-Raphson iteration to improve the precision. + // Thanks to Hensel's lifting lemma, this also works in modular + // arithmetic, doubling the correct bits in each step. + inv.Mul(&inv, tmp.Sub(&two, tmp1.Mul(&denominatorTmp, &inv))) // inverse mod 2**8 + inv.Mul(&inv, tmp.Sub(&two, tmp1.Mul(&denominatorTmp, &inv))) // inverse mod 2**16 + inv.Mul(&inv, tmp.Sub(&two, tmp1.Mul(&denominatorTmp, &inv))) // inverse mod 2**32 + inv.Mul(&inv, tmp.Sub(&two, tmp1.Mul(&denominatorTmp, &inv))) // inverse mod 2**64 + inv.Mul(&inv, tmp.Sub(&two, tmp1.Mul(&denominatorTmp, &inv))) // inverse mod 2**128 + inv.Mul(&inv, tmp.Sub(&two, tmp1.Mul(&denominatorTmp, &inv))) // inverse mod 2**256 + + // Because the division is now exact we can divide by multiplying + // with the modular inverse of denominator. This will give us the + // correct result modulo 2**256. Since the precoditions guarantee + // that the outcome is less than 2**256, this is the final result. + // We don't need to compute the high bits of the result and prod1 + // is no longer required. + result.Mul(&prod0, &inv) + return nil +} + // Calculates floor(a×b÷denominator) with full precision func MulDiv(a, b, denominator *uint256.Int) (*uint256.Int, error) { // the product can overflow so need to use big.Int here @@ -46,11 +170,10 @@ func MulDiv(a, b, denominator *uint256.Int) (*uint256.Int, error) { } // Returns ceil(x / y) -func DivRoundingUp(a, denominator *uint256.Int) *uint256.Int { - var result, rem uint256.Int +func DivRoundingUp(a, denominator, result *uint256.Int) { + var rem uint256.Int result.DivMod(a, denominator, &rem) if !rem.IsZero() { - result.AddUint64(&result, 1) + result.AddUint64(result, 1) } - return &result } diff --git a/utils/full_math_test.go b/utils/full_math_test.go index fa694ed..d215867 100644 --- a/utils/full_math_test.go +++ b/utils/full_math_test.go @@ -2,6 +2,7 @@ package utils import ( "fmt" + "math/rand" "testing" "github.com/holiman/uint256" @@ -22,6 +23,8 @@ func TestMulDiv(t *testing.T) { {"0x100000000000000000000000000000000", "0x80000000000000000000000000000000", "0x180000000000000000000000000000000", "113427455640312821154458202477256070485"}, {"0x100000000000000000000000000000000", "0x2300000000000000000000000000000000", "0x800000000000000000000000000000000", "1488735355279105777652263907513985925120"}, {"0x100000000000000000000000000000000", "0x3e800000000000000000000000000000000", "0xbb800000000000000000000000000000000", "113427455640312821154458202477256070485"}, + + {"0x61ae64157b363469ec1e000000000000000000000000", "0x5d5502f19f7baee2e5fa2", "0x69b797741ba66bda48a81e9", "126036350226489723925526476841950279379016090973169"}, } for i, tt := range tests { t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { @@ -30,6 +33,13 @@ func TestMulDiv(t *testing.T) { uint256.MustFromHex(tt.deno)) require.Nil(t, err) assert.Equal(t, tt.expResult, r.Dec()) + + // v2 + var rv2 Uint256 + err = MulDivV2(uint256.MustFromHex(tt.a), uint256.MustFromHex(tt.b), + uint256.MustFromHex(tt.deno), &rv2, nil) + require.Nil(t, err) + assert.Equal(t, tt.expResult, rv2.Dec()) }) } @@ -49,6 +59,54 @@ func TestMulDiv(t *testing.T) { uint256.MustFromHex(tt.a), uint256.MustFromHex(tt.b), uint256.MustFromHex(tt.deno)) require.NotNil(t, err) + + // v2 + var rv2 Uint256 + err = MulDivV2(uint256.MustFromHex(tt.a), uint256.MustFromHex(tt.b), + uint256.MustFromHex(tt.deno), &rv2, nil) + require.NotNil(t, err) + }) + } +} + +func RandNumberHexString(maxLen int) string { + sLen := rand.Intn(maxLen) + 1 + var s string + for i := 0; i < sLen; i++ { + var c int + if i == 0 { + c = rand.Intn(15) + 1 + } else { + c = rand.Intn(16) + } + s = fmt.Sprintf("%s%x", s, c) + } + return s +} + +func RandUint256() *Uint256 { + s := RandNumberHexString(64) + return uint256.MustFromHex("0x" + s) +} + +func TestMulDivV2(t *testing.T) { + for i := 0; i < 500; i++ { + a := RandUint256() + b := RandUint256() + deno := RandUint256() + + t.Run(fmt.Sprintf("test %s %s %s", a.Hex(), b.Hex(), deno.Hex()), func(t *testing.T) { + r, err := MulDiv(a, b, deno) + + var rv2 Uint256 + errv2 := MulDivV2(a, b, deno, &rv2, nil) + + if err != nil { + require.NotNil(t, errv2) + } else { + require.Nil(t, errv2) + assert.Equal(t, r.Dec(), rv2.Dec()) + } }) } } @@ -66,6 +124,8 @@ func TestMulDivRoundingUp(t *testing.T) { {"0x100000000000000000000000000000000", "0x80000000000000000000000000000000", "0x180000000000000000000000000000000", "113427455640312821154458202477256070486"}, {"0x100000000000000000000000000000000", "0x2300000000000000000000000000000000", "0x800000000000000000000000000000000", "1488735355279105777652263907513985925120"}, {"0x100000000000000000000000000000000", "0x3e800000000000000000000000000000000", "0xbb800000000000000000000000000000000", "113427455640312821154458202477256070486"}, + + {"0x2a60f4810d72e89eaee06f20122f1de80adc64777e121", "0xfd21718acef075500c6395ba922064220", "0xd195e7433221b9e4b6ef3f19b457c9c9797ae6b5eaacb402113dce147e97979f", "14406918379743960"}, } for i, tt := range tests { t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { @@ -74,6 +134,14 @@ func TestMulDivRoundingUp(t *testing.T) { uint256.MustFromHex(tt.deno)) require.Nil(t, err) assert.Equal(t, tt.expResult, r.Dec()) + + // v2 + var rv2 Uint256 + err = MulDivRoundingUpV2( + uint256.MustFromHex(tt.a), uint256.MustFromHex(tt.b), + uint256.MustFromHex(tt.deno), &rv2) + require.Nil(t, err) + assert.Equal(t, tt.expResult, rv2.Dec()) }) } @@ -95,6 +163,35 @@ func TestMulDivRoundingUp(t *testing.T) { uint256.MustFromHex(tt.a), uint256.MustFromHex(tt.b), uint256.MustFromHex(tt.deno)) require.NotNil(t, err, x) + + // v2 + var rv2 Uint256 + err = MulDivRoundingUpV2( + uint256.MustFromHex(tt.a), uint256.MustFromHex(tt.b), + uint256.MustFromHex(tt.deno), &rv2) + require.NotNil(t, err) + }) + } +} + +func TestMulDivRoundingUpV2(t *testing.T) { + for i := 0; i < 500; i++ { + a := RandUint256() + b := RandUint256() + deno := RandUint256() + + t.Run(fmt.Sprintf("test %s %s %s", a.Hex(), b.Hex(), deno.Hex()), func(t *testing.T) { + r, err := MulDivRoundingUp(a, b, deno) + + var rv2 Uint256 + errv2 := MulDivRoundingUpV2(a, b, deno, &rv2) + + if err != nil { + require.NotNil(t, errv2) + } else { + require.Nil(t, errv2) + assert.Equal(t, r.Dec(), rv2.Dec()) + } }) } } diff --git a/utils/int_types.go b/utils/int_types.go index f790ea8..4b34d9d 100644 --- a/utils/int_types.go +++ b/utils/int_types.go @@ -38,6 +38,13 @@ func ToInt256(value *Uint256, result *Int256) error { return nil } +func ToUInt256(value *Int256, result *Uint256) error { + var ba [32]byte + value.WriteToArray32(&ba) + result.SetBytes32(ba[:]) + return nil +} + // https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/SafeCast.sol func CheckToUint160(value *Uint256) error { // we're using same type for Uint256 and Uint160, so use the original for now diff --git a/utils/most_significant_bit.go b/utils/most_significant_bit.go index e7d17ff..ebf4a22 100644 --- a/utils/most_significant_bit.go +++ b/utils/most_significant_bit.go @@ -25,7 +25,7 @@ var powersOf2 = []powerOf2{ } func MostSignificantBit(x *uint256.Int) (uint, error) { - if x.Sign() == 0 { + if x.IsZero() { return 0, ErrInvalidInput } diff --git a/utils/sqrtprice_math.go b/utils/sqrtprice_math.go index c8b6c6d..700716c 100644 --- a/utils/sqrtprice_math.go +++ b/utils/sqrtprice_math.go @@ -29,11 +29,13 @@ func addIn256(x, y, sum *uint256.Int) *uint256.Int { // deprecated func GetAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity *big.Int, roundUp bool) *big.Int { - res, err := GetAmount0DeltaV2( + var res uint256.Int + err := GetAmount0DeltaV2( uint256.MustFromBig(sqrtRatioAX96), uint256.MustFromBig(sqrtRatioBX96), uint256.MustFromBig(liquidity), roundUp, + &res, ) if err != nil { panic(err) @@ -41,7 +43,7 @@ func GetAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity *big.Int, roundUp b return res.ToBig() } -func GetAmount0DeltaV2(sqrtRatioAX96, sqrtRatioBX96 *Uint160, liquidity *Uint128, roundUp bool) (*uint256.Int, error) { +func GetAmount0DeltaV2(sqrtRatioAX96, sqrtRatioBX96 *Uint160, liquidity *Uint128, roundUp bool, result *Uint256) error { // https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/libraries/SqrtPriceMath.sol#L159 if sqrtRatioAX96.Cmp(sqrtRatioBX96) > 0 { sqrtRatioAX96, sqrtRatioBX96 = sqrtRatioBX96, sqrtRatioAX96 @@ -52,28 +54,33 @@ func GetAmount0DeltaV2(sqrtRatioAX96, sqrtRatioBX96 *Uint160, liquidity *Uint128 numerator2.Sub(sqrtRatioBX96, sqrtRatioAX96) if roundUp { - deno, err := MulDivRoundingUp(&numerator1, &numerator2, sqrtRatioBX96) + var deno Uint256 + err := MulDivRoundingUpV2(&numerator1, &numerator2, sqrtRatioBX96, &deno) if err != nil { - return nil, err + return err } - return DivRoundingUp(deno, sqrtRatioAX96), nil + DivRoundingUp(&deno, sqrtRatioAX96, result) + return nil } // : FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96; - tmp, err := MulDiv(&numerator1, &numerator2, sqrtRatioBX96) + var tmp Uint256 + err := MulDivV2(&numerator1, &numerator2, sqrtRatioBX96, &tmp, nil) if err != nil { - return nil, err + return err } - result := new(uint256.Int).Div(tmp, sqrtRatioAX96) - return result, nil + result.Div(&tmp, sqrtRatioAX96) + return nil } // deprecated func GetAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity *big.Int, roundUp bool) *big.Int { - res, err := GetAmount1DeltaV2( + var res Uint256 + err := GetAmount1DeltaV2( uint256.MustFromBig(sqrtRatioAX96), uint256.MustFromBig(sqrtRatioBX96), uint256.MustFromBig(liquidity), roundUp, + &res, ) if err != nil { panic(err) @@ -81,7 +88,7 @@ func GetAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity *big.Int, roundUp b return res.ToBig() } -func GetAmount1DeltaV2(sqrtRatioAX96, sqrtRatioBX96 *Uint160, liquidity *Uint128, roundUp bool) (*uint256.Int, error) { +func GetAmount1DeltaV2(sqrtRatioAX96, sqrtRatioBX96 *Uint160, liquidity *Uint128, roundUp bool, result *Uint256) error { // https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/libraries/SqrtPriceMath.sol#L188 if sqrtRatioAX96.Cmp(sqrtRatioBX96) > 0 { sqrtRatioAX96, sqrtRatioBX96 = sqrtRatioBX96, sqrtRatioAX96 @@ -90,41 +97,50 @@ func GetAmount1DeltaV2(sqrtRatioAX96, sqrtRatioBX96 *Uint160, liquidity *Uint128 var diff uint256.Int diff.Sub(sqrtRatioBX96, sqrtRatioAX96) if roundUp { - return MulDivRoundingUp(liquidity, &diff, constants.Q96U256) + err := MulDivRoundingUpV2(liquidity, &diff, constants.Q96U256, result) + if err != nil { + return err + } + return nil } // : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); - return MulDiv(liquidity, &diff, constants.Q96U256) + err := MulDivV2(liquidity, &diff, constants.Q96U256, result, nil) + if err != nil { + return err + } + return nil } -func GetNextSqrtPriceFromInput(sqrtPX96 *Uint160, liquidity *Uint128, amountIn *uint256.Int, zeroForOne bool) (*Uint160, error) { +func GetNextSqrtPriceFromInput(sqrtPX96 *Uint160, liquidity *Uint128, amountIn *uint256.Int, zeroForOne bool, result *Uint160) error { if sqrtPX96.Sign() <= 0 { - return nil, ErrSqrtPriceLessThanZero + return ErrSqrtPriceLessThanZero } if liquidity.Sign() <= 0 { - return nil, ErrLiquidityLessThanZero + return ErrLiquidityLessThanZero } if zeroForOne { - return getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true) + return getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true, result) } - return getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true) + return getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true, result) } -func GetNextSqrtPriceFromOutput(sqrtPX96 *Uint160, liquidity *Uint128, amountOut *uint256.Int, zeroForOne bool) (*Uint160, error) { +func GetNextSqrtPriceFromOutput(sqrtPX96 *Uint160, liquidity *Uint128, amountOut *uint256.Int, zeroForOne bool, result *Uint160) error { if sqrtPX96.Sign() <= 0 { - return nil, ErrSqrtPriceLessThanZero + return ErrSqrtPriceLessThanZero } if liquidity.Sign() <= 0 { - return nil, ErrLiquidityLessThanZero + return ErrLiquidityLessThanZero } if zeroForOne { - return getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false) + return getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false, result) } - return getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false) + return getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false, result) } -func getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96 *Uint160, liquidity *Uint128, amount *uint256.Int, add bool) (*Uint160, error) { +func getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96 *Uint160, liquidity *Uint128, amount *uint256.Int, add bool, result *Uint160) error { if amount.IsZero() { - return sqrtPX96, nil + result.Set(sqrtPX96) + return nil } var numerator1, denominator, product, tmp uint256.Int @@ -134,25 +150,28 @@ func getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96 *Uint160, liquidity *Uint128 if tmp.Div(&product, amount).Cmp(sqrtPX96) == 0 { addIn256(&numerator1, &product, &denominator) if denominator.Cmp(&numerator1) >= 0 { - return MulDivRoundingUp(&numerator1, sqrtPX96, &denominator) + err := MulDivRoundingUpV2(&numerator1, sqrtPX96, &denominator, result) + return err } } tmp.Div(&numerator1, sqrtPX96) tmp.Add(&tmp, amount) - return DivRoundingUp(&numerator1, &tmp), nil + DivRoundingUp(&numerator1, &tmp, result) + return nil } else { if tmp.Div(&product, amount).Cmp(sqrtPX96) != 0 { - return nil, ErrInvariant + return ErrInvariant } if numerator1.Cmp(&product) <= 0 { - return nil, ErrInvariant + return ErrInvariant } denominator.Sub(&numerator1, &product) - return MulDivRoundingUp(&numerator1, sqrtPX96, &denominator) + err := MulDivRoundingUpV2(&numerator1, sqrtPX96, &denominator, result) + return err } } -func getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96 *Uint160, liquidity *Uint128, amount *uint256.Int, add bool) (*Uint160, error) { +func getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96 *Uint160, liquidity *Uint128, amount *uint256.Int, add bool, result *Uint160) error { if add { var quotient, tmp uint256.Int if amount.Cmp(MaxUint160) <= 0 { @@ -164,23 +183,26 @@ func getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96 *Uint160, liquidity *Uint1 } _, overflow := quotient.AddOverflow("ient, sqrtPX96) if overflow { - return nil, ErrAddOverflow + return ErrAddOverflow } err := CheckToUint160("ient) if err != nil { - return nil, err + return err } - return "ient, nil + result.Set("ient) + return nil } - quotient, err := MulDivRoundingUp(amount, constants.Q96U256, liquidity) + var quotient Uint256 + err := MulDivRoundingUpV2(amount, constants.Q96U256, liquidity, "ient) if err != nil { - return nil, err + return err } - if sqrtPX96.Cmp(quotient) <= 0 { - return nil, ErrInvariant + if sqrtPX96.Cmp("ient) <= 0 { + return ErrInvariant } - quotient.Sub(sqrtPX96, quotient) + quotient.Sub(sqrtPX96, "ient) // always fits 160 bits - return quotient, nil + result.Set("ient) + return nil } diff --git a/utils/sqrtprice_math_test.go b/utils/sqrtprice_math_test.go index 843e863..4376f6d 100644 --- a/utils/sqrtprice_math_test.go +++ b/utils/sqrtprice_math_test.go @@ -30,11 +30,12 @@ func TestGetNextSqrtPriceFromInput(t *testing.T) { {"0x" + p1.Text(16), "0x8ac7230489e80000", "0x10000000000000000000000000", true, "624999999995069620"}, {"0x" + p1.Text(16), "0x1", "0x8000000000000000000000000000000000000000000000000000000000000000", true, "1"}, } + var r Uint160 for i, tt := range tests { t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { - r, err := GetNextSqrtPriceFromInput( + err := GetNextSqrtPriceFromInput( uint256.MustFromHex(tt.price), uint256.MustFromHex(tt.liquidity), - uint256.MustFromHex(tt.amount), tt.zeroForOne) + uint256.MustFromHex(tt.amount), tt.zeroForOne, &r) require.Nil(t, err) assert.Equal(t, tt.expResult, r.Dec()) }) @@ -51,9 +52,9 @@ func TestGetNextSqrtPriceFromInput(t *testing.T) { } for i, tt := range failTests { t.Run(fmt.Sprintf("fail test %d", i), func(t *testing.T) { - _, err := GetNextSqrtPriceFromInput( + err := GetNextSqrtPriceFromInput( uint256.MustFromHex(tt.price), uint256.MustFromHex(tt.liquidity), - uint256.MustFromHex(tt.amount), tt.zeroForOne) + uint256.MustFromHex(tt.amount), tt.zeroForOne, &r) require.NotNil(t, err) }) } @@ -76,11 +77,12 @@ func TestGetNextSqrtPriceFromOutput(t *testing.T) { {"0x" + p1.Text(16), "0xde0b6b3a7640000", "0x16345785d8a0000", false, "88031291682515930659493278152"}, {"0x" + p1.Text(16), "0xde0b6b3a7640000", "0x16345785d8a0000", true, "71305346262837903834189555302"}, } + var r Uint160 for i, tt := range tests { t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { - r, err := GetNextSqrtPriceFromOutput( + err := GetNextSqrtPriceFromOutput( uint256.MustFromHex(tt.price), uint256.MustFromHex(tt.liquidity), - uint256.MustFromHex(tt.amount), tt.zeroForOne) + uint256.MustFromHex(tt.amount), tt.zeroForOne, &r) require.Nil(t, err) assert.Equal(t, tt.expResult, r.Dec()) }) @@ -104,9 +106,9 @@ func TestGetNextSqrtPriceFromOutput(t *testing.T) { } for i, tt := range failTests { t.Run(fmt.Sprintf("fail test %d", i), func(t *testing.T) { - _, err := GetNextSqrtPriceFromOutput( + err := GetNextSqrtPriceFromOutput( uint256.MustFromHex(tt.price), uint256.MustFromHex(tt.liquidity), - uint256.MustFromHex(tt.amount), tt.zeroForOne) + uint256.MustFromHex(tt.amount), tt.zeroForOne, &r) require.NotNil(t, err) }) } @@ -135,11 +137,12 @@ func TestGetAmount0Delta(t *testing.T) { {"0x" + p4.Text(16), "0x" + p5.Text(16), "0xde0b6b3a7640000", true, "24869"}, {"0x" + p4.Text(16), "0x" + p5.Text(16), "0xde0b6b3a7640000", false, "24868"}, } + var r Uint256 for i, tt := range tests { t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { - r, err := GetAmount0DeltaV2( + err := GetAmount0DeltaV2( uint256.MustFromHex(tt.price), uint256.MustFromHex(tt.liquidity), - uint256.MustFromHex(tt.amount), tt.zeroForOne) + uint256.MustFromHex(tt.amount), tt.zeroForOne, &r) require.Nil(t, err) assert.Equal(t, tt.expResult, r.Dec()) }) @@ -167,11 +170,12 @@ func TestGetAmount1Delta(t *testing.T) { {"0x" + p4.Text(16), "0x" + p1.Text(16), "0xde0b6b3a7640000", true, "90909090909090910"}, {"0x" + p4.Text(16), "0x" + p1.Text(16), "0xde0b6b3a7640000", false, "90909090909090909"}, } + var r Uint256 for i, tt := range tests { t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { - r, err := GetAmount1DeltaV2( + err := GetAmount1DeltaV2( uint256.MustFromHex(tt.price), uint256.MustFromHex(tt.liquidity), - uint256.MustFromHex(tt.amount), tt.zeroForOne) + uint256.MustFromHex(tt.amount), tt.zeroForOne, &r) require.Nil(t, err) assert.Equal(t, tt.expResult, r.Dec()) }) @@ -181,18 +185,20 @@ func TestGetAmount1Delta(t *testing.T) { func TestSwap(t *testing.T) { // sqrtP * sqrtQ overflows - sqrtQ, err := GetNextSqrtPriceFromInput( + var sqrtQ Uint160 + err := GetNextSqrtPriceFromInput( uint256.MustFromDecimal("1025574284609383690408304870162715216695788925244"), uint256.MustFromDecimal("50015962439936049619261659728067971248"), - uint256.MustFromDecimal("406"), true) + uint256.MustFromDecimal("406"), true, &sqrtQ) require.Nil(t, err) require.Equal(t, "1025574284609383582644711336373707553698163132913", sqrtQ.Dec()) - amount0Delta, err := GetAmount0DeltaV2( - sqrtQ, + var amount0Delta Uint256 + err = GetAmount0DeltaV2( + &sqrtQ, uint256.MustFromDecimal("1025574284609383690408304870162715216695788925244"), - uint256.MustFromDecimal("50015962439936049619261659728067971248"), true) + uint256.MustFromDecimal("50015962439936049619261659728067971248"), true, &amount0Delta) require.Nil(t, err) assert.Equal(t, "406", amount0Delta.Dec()) diff --git a/utils/swap_math.go b/utils/swap_math.go index 51568af..d079531 100644 --- a/utils/swap_math.go +++ b/utils/swap_math.go @@ -1,15 +1,10 @@ package utils import ( - "math/big" - - "github.com/KyberNetwork/int256" "github.com/KyberNetwork/uniswapv3-sdk-uint256/constants" "github.com/holiman/uint256" ) -var MaxFee = new(big.Int).Exp(big.NewInt(10), big.NewInt(6), nil) - const MaxFeeInt = 1000000 var MaxFeeUint256 = uint256.NewInt(MaxFeeInt) @@ -18,20 +13,19 @@ func ComputeSwapStep( sqrtRatioCurrentX96, sqrtRatioTargetX96 *Uint160, liquidity *Uint128, - amountRemaining *int256.Int, + amountRemaining *Int256, feePips constants.FeeAmount, -) (sqrtRatioNextX96 *Uint160, amountIn, amountOut, feeAmount *uint256.Int, err error) { + + sqrtRatioNextX96 *Uint160, amountIn, amountOut, feeAmount *Uint256, +) error { zeroForOne := sqrtRatioCurrentX96.Cmp(sqrtRatioTargetX96) >= 0 exactIn := amountRemaining.Sign() >= 0 var amountRemainingU uint256.Int if exactIn { - amountRemainingBI := amountRemaining.ToBig() - amountRemainingU.SetFromBig(amountRemainingBI) // TODO: optimize this + ToUInt256(amountRemaining, &amountRemainingU) } else { - amountRemaining1 := new(int256.Int).Set(amountRemaining) - amountRemainingBI := amountRemaining1.ToBig() - amountRemainingU.SetFromBig(amountRemainingBI) // TODO: optimize this + ToUInt256(amountRemaining, &amountRemainingU) amountRemainingU.Neg(&amountRemainingU) } @@ -42,42 +36,42 @@ func ComputeSwapStep( tmp.Mul(&amountRemainingU, &maxFeeMinusFeePips) amountRemainingLessFee.Div(&tmp, MaxFeeUint256) if zeroForOne { - amountIn, err = GetAmount0DeltaV2(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, true) + err := GetAmount0DeltaV2(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, true, amountIn) if err != nil { - return + return err } } else { - amountIn, err = GetAmount1DeltaV2(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, true) + err := GetAmount1DeltaV2(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, true, amountIn) if err != nil { - return + return err } } if amountRemainingLessFee.Cmp(amountIn) >= 0 { - sqrtRatioNextX96 = sqrtRatioTargetX96 + sqrtRatioNextX96.Set(sqrtRatioTargetX96) } else { - sqrtRatioNextX96, err = GetNextSqrtPriceFromInput(sqrtRatioCurrentX96, liquidity, &amountRemainingLessFee, zeroForOne) + err := GetNextSqrtPriceFromInput(sqrtRatioCurrentX96, liquidity, &amountRemainingLessFee, zeroForOne, sqrtRatioNextX96) if err != nil { - return + return err } } } else { if zeroForOne { - amountOut, err = GetAmount1DeltaV2(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, false) + err := GetAmount1DeltaV2(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, false, amountOut) if err != nil { - return + return err } } else { - amountOut, err = GetAmount0DeltaV2(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, false) + err := GetAmount0DeltaV2(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, false, amountOut) if err != nil { - return + return err } } if amountRemainingU.Cmp(amountOut) >= 0 { - sqrtRatioNextX96 = sqrtRatioTargetX96 + sqrtRatioNextX96.Set(sqrtRatioTargetX96) } else { - sqrtRatioNextX96, err = GetNextSqrtPriceFromOutput(sqrtRatioCurrentX96, liquidity, &amountRemainingU, zeroForOne) + err := GetNextSqrtPriceFromOutput(sqrtRatioCurrentX96, liquidity, &amountRemainingU, zeroForOne, sqrtRatioNextX96) if err != nil { - return + return err } } } @@ -86,45 +80,45 @@ func ComputeSwapStep( if zeroForOne { if !(max && exactIn) { - amountIn, err = GetAmount0DeltaV2(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, true) + err := GetAmount0DeltaV2(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, true, amountIn) if err != nil { - return + return err } } if !(max && !exactIn) { - amountOut, err = GetAmount1DeltaV2(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, false) + err := GetAmount1DeltaV2(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, false, amountOut) if err != nil { - return + return err } } } else { if !(max && exactIn) { - amountIn, err = GetAmount1DeltaV2(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, true) + err := GetAmount1DeltaV2(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, true, amountIn) if err != nil { - return + return err } } if !(max && !exactIn) { - amountOut, err = GetAmount0DeltaV2(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, false) + err := GetAmount0DeltaV2(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, false, amountOut) if err != nil { - return + return err } } } if !exactIn && amountOut.Cmp(&amountRemainingU) > 0 { - amountOut = &amountRemainingU + amountOut.Set(&amountRemainingU) } if exactIn && sqrtRatioNextX96.Cmp(sqrtRatioTargetX96) != 0 { // we didn't reach the target, so take the remainder of the maximum input as fee - feeAmount = new(uint256.Int).Sub(&amountRemainingU, amountIn) + feeAmount.Sub(&amountRemainingU, amountIn) } else { - feeAmount, err = MulDivRoundingUp(amountIn, uint256.NewInt(uint64(feePips)), &maxFeeMinusFeePips) + err := MulDivRoundingUpV2(amountIn, uint256.NewInt(uint64(feePips)), &maxFeeMinusFeePips, feeAmount) if err != nil { - return + return err } } - return + return nil } diff --git a/utils/swap_math_test.go b/utils/swap_math_test.go index 7ca3a6c..4b993da 100644 --- a/utils/swap_math_test.go +++ b/utils/swap_math_test.go @@ -57,14 +57,17 @@ func TestComputeSwapStep(t *testing.T) { {"20282409603651670423947251286016", "18254168643286503381552526157414", "1024", "-263000", 3000, "1", "26214", "1", "="}, } + var sqrtRatioNextX96 Uint160 + var amountIn, amountOut, feeAmount Uint256 for i, tt := range tests { t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { - sqrtRatioNextX96, amountIn, amountOut, feeAmount, err := ComputeSwapStep( + err := ComputeSwapStep( uint256.MustFromDecimal(tt.price), uint256.MustFromDecimal(tt.priceTarget), uint256.MustFromDecimal(tt.liquidity), int256.MustFromDec(tt.amount), tt.fee, + &sqrtRatioNextX96, &amountIn, &amountOut, &feeAmount, ) require.Nil(t, err) if tt.expNextPrice == "=" { diff --git a/utils/tick_math.go b/utils/tick_math.go index 107ca10..01464ad 100644 --- a/utils/tick_math.go +++ b/utils/tick_math.go @@ -61,7 +61,8 @@ var ( // deprecated func GetSqrtRatioAtTick(tick int) (*big.Int, error) { - res, err := GetSqrtRatioAtTickV2(tick) + var res Uint160 + err := GetSqrtRatioAtTickV2(tick, &res) return res.ToBig(), err } @@ -69,15 +70,15 @@ func GetSqrtRatioAtTick(tick int) (*big.Int, error) { * Returns the sqrt ratio as a Q64.96 for the given tick. The sqrt ratio is computed as sqrt(1.0001)^tick * @param tick the tick for which to compute the sqrt ratio */ -func GetSqrtRatioAtTickV2(tick int) (*Uint160, error) { +func GetSqrtRatioAtTickV2(tick int, result *Uint160) error { if tick < MinTick || tick > MaxTick { - return nil, ErrInvalidTick + return ErrInvalidTick } absTick := tick if tick < 0 { absTick = -tick } - var ratio, tmp Uint256 + var ratio Uint256 if absTick&0x1 != 0 { ratio.Set(sqrtConst1) } else { @@ -141,18 +142,18 @@ func GetSqrtRatioAtTickV2(tick int) (*Uint160, error) { mulShift(&ratio, sqrtConst21) } if tick > 0 { - tmp.Div(MaxUint256, &ratio) - ratio.Set(&tmp) + result.Div(MaxUint256, &ratio) + ratio.Set(result) } // back to Q96 var rem Uint256 - tmp.DivMod(&ratio, Q32U256, &rem) + result.DivMod(&ratio, Q32U256, &rem) if !rem.IsZero() { - tmp.AddUint64(&tmp, 1) - return &tmp, nil + result.AddUint64(result, 1) + return nil } else { - return &tmp, nil + return nil } } @@ -216,7 +217,8 @@ func GetTickAtSqrtRatioV2(sqrtRatioX96 *Uint160) (int, error) { return int(tickLow), nil } - sqrtRatio, err := GetSqrtRatioAtTickV2(int(tickHigh)) + var sqrtRatio Uint160 + err = GetSqrtRatioAtTickV2(int(tickHigh), &sqrtRatio) if err != nil { return 0, err } diff --git a/utils/tick_math_test.go b/utils/tick_math_test.go index 91f6bef..a1bb6b6 100644 --- a/utils/tick_math_test.go +++ b/utils/tick_math_test.go @@ -19,8 +19,9 @@ func TestGetSqrtRatioAtTick(t *testing.T) { rmax, _ := GetSqrtRatioAtTick(MinTick) assert.Equal(t, rmax, MinSqrtRatio, "returns the correct value for min tick") - r, _ := GetSqrtRatioAtTickV2(MinTick + 1) - assert.Equal(t, uint256.NewInt(4295343490), r, "returns the correct value for min tick + 1") + var r Uint160 + _ = GetSqrtRatioAtTickV2(MinTick+1, &r) + assert.Equal(t, uint256.NewInt(4295343490), &r, "returns the correct value for min tick + 1") r0, _ := GetSqrtRatioAtTick(0) assert.Equal(t, r0, new(big.Int).Lsh(constants.One, 96), "returns the correct value for tick 0") @@ -28,11 +29,11 @@ func TestGetSqrtRatioAtTick(t *testing.T) { rmin, _ := GetSqrtRatioAtTick(MaxTick) assert.Equal(t, rmin, MaxSqrtRatio, "returns the correct value for max tick") - r, _ = GetSqrtRatioAtTickV2(MaxTick - 1) - assert.Equal(t, uint256.MustFromDecimal("1461373636630004318706518188784493106690254656249"), r, "returns the correct value for max tick - 1") + _ = GetSqrtRatioAtTickV2(MaxTick-1, &r) + assert.Equal(t, uint256.MustFromDecimal("1461373636630004318706518188784493106690254656249"), &r, "returns the correct value for max tick - 1") - r, _ = GetSqrtRatioAtTickV2(MaxTick) - assert.Equal(t, uint256.MustFromDecimal("1461446703485210103287273052203988822378723970342"), r, "returns the correct value for max tick") + _ = GetSqrtRatioAtTickV2(MaxTick, &r) + assert.Equal(t, uint256.MustFromDecimal("1461446703485210103287273052203988822378723970342"), &r, "returns the correct value for max tick") } func TestGetTickAtSqrtRatio(t *testing.T) {