diff --git a/_deploy/p/gnoswap/pool/__TEST_swap_math_test.gnoA b/_deploy/p/gnoswap/pool/__TEST_swap_math_test.gnoA deleted file mode 100644 index b34032bb6..000000000 --- a/_deploy/p/gnoswap/pool/__TEST_swap_math_test.gnoA +++ /dev/null @@ -1,202 +0,0 @@ -package pool - -import ( - "testing" - - i256 "gno.land/p/gnoswap/int256" - u256 "gno.land/p/gnoswap/uint256" -) - -var ( - amountIn_i256 *i256.Int - amountOut *u256.Uint - zeroForOne bool - expected *u256.Uint - rst bool - price *u256.Uint - priceTarget *u256.Uint - liquidity *u256.Uint - fee uint64 - amountIn_String string - amountOut_String string - sqrtQ_String string - feeAmount_String string -) - -func TestSwapMathComputeSwapStepStr_1(t *testing.T) { - var amountIn_feeAmount *u256.Uint - var priceAfterWholeInputAmount *u256.Uint - // exact amount in that gets capped at price target in one for zero - - price = u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - priceTarget = u256.MustFromDecimal("79623317895830914510639640423") // encodePriceSqrt(101,100) = 79623317895830914510639640423 - liquidity = u256.MustFromDecimal("2000000000000000000") // 2e18 - amountIn_i256 = i256.MustFromDecimal("1000000000000000000") // 1e18 - fee = 600 - zeroForOne = false - - sqrtQ_String, amountIn_String, amountOut_String, feeAmount_String := SwapMathComputeSwapStepStr(price, priceTarget, liquidity, amountIn_i256, fee) - - shouldEQ(t, amountIn_String, "9975124224178055") - shouldEQ(t, feeAmount_String, "5988667735148") - shouldEQ(t, amountOut_String, "9925619580021728") - amountIn_feeAmount = u256.MustFromDecimal(amountIn_String) - amountIn_feeAmount.Add(amountIn_feeAmount, u256.MustFromDecimal(feeAmount_String)) - - if amountIn_feeAmount.Cmp(u256.MustFromDecimal("1000000000000000000")) > 0 { - t.Errorf("entire amount is not used") - } - - priceAfterWholeInputAmount = sqrtPriceMathGetNextSqrtPriceFromInput(price, liquidity, u256.MustFromDecimal("1000000000000000000"), zeroForOne) - - shouldEQ(t, sqrtQ_String, priceTarget.ToString()) - if u256.MustFromDecimal(sqrtQ_String).Cmp(priceAfterWholeInputAmount) > 0 { - t.Errorf("price is less than price after whole input amount") - } -} - -func TestSwapMathComputeSwapStepStr_2(t *testing.T) { - var priceAfterWholeInputAmount *u256.Uint - // exact amount out that gets capped at price target in one for zero - - price = u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - priceTarget = u256.MustFromDecimal("79623317895830914510639640423") // encodePriceSqrt(101,100) = 79623317895830914510639640423 - liquidity = u256.MustFromDecimal("2000000000000000000") // 2e18 - amountIn_i256 = i256.MustFromDecimal("-1000000000000000000") // -1e18 - fee = 600 - zeroForOne = false - - sqrtQ_String, amountIn_String, amountOut_String, feeAmount_String := SwapMathComputeSwapStepStr(price, priceTarget, liquidity, amountIn_i256, fee) - - shouldEQ(t, amountIn_String, "9975124224178055") - shouldEQ(t, feeAmount_String, "5988667735148") - shouldEQ(t, amountOut_String, "9925619580021728") - - if u256.MustFromDecimal(amountOut_String).Cmp(u256.MustFromDecimal("1000000000000000000")) >= 0 { - t.Errorf("entire amount out is not returned") - } - - priceAfterWholeInputAmount = sqrtPriceMathGetNextSqrtPriceFromInput(price, liquidity, u256.MustFromDecimal("1000000000000000000"), zeroForOne) - shouldEQ(t, sqrtQ_String, priceTarget.ToString()) - if u256.MustFromDecimal(sqrtQ_String).Cmp(priceAfterWholeInputAmount) > 0 { - t.Errorf("price is less than price after whole output amount") - } -} - -func TestSwapMathComputeSwapStepStr_3(t *testing.T) { - var amountIn_feeAmount *u256.Uint - var priceAfterWholeInputAmount *u256.Uint - // exact amount in that is fully spent in one for zero - - price = u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - priceTarget = u256.MustFromDecimal("792281625142643375935439503360") // encodePriceSqrt(1000,100) = 792281625142643375935439503360 - liquidity = u256.MustFromDecimal("2000000000000000000") // 2e18 - amountIn_i256 = i256.MustFromDecimal("1000000000000000000") // 1e18 - fee = 600 - zeroForOne = false - - sqrtQ_String, amountIn_String, amountOut_String, feeAmount_String := SwapMathComputeSwapStepStr(price, priceTarget, liquidity, amountIn_i256, fee) - - shouldEQ(t, amountIn_String, "999400000000000000") - shouldEQ(t, feeAmount_String, "600000000000000") - shouldEQ(t, amountOut_String, "666399946655997866") - amountIn_feeAmount = u256.MustFromDecimal(amountIn_String) - amountIn_feeAmount.Add(amountIn_feeAmount, u256.MustFromDecimal(feeAmount_String)) - shouldEQ(t, amountIn_feeAmount.ToString(), "1000000000000000000") - - priceAfterWholeInputAmount = sqrtPriceMathGetNextSqrtPriceFromInput(price, liquidity, u256.MustFromDecimal("999400000000000000"), zeroForOne) - shouldEQ(t, sqrtQ_String, priceAfterWholeInputAmount.ToString()) - if u256.MustFromDecimal(sqrtQ_String).Cmp(priceTarget) > 0 { - t.Errorf("price does not reach price target") - } -} - -func TestSwapMathComputeSwapStepStr_4(t *testing.T) { - // amount out is capped at the desired amount out - - price = u256.MustFromDecimal("417332158212080721273783715441582") - priceTarget = u256.MustFromDecimal("1452870262520218020823638996") - liquidity = u256.MustFromDecimal("159344665391607089467575320103") - amountIn_i256 = i256.MustFromDecimal("-1") - fee = 1 - - sqrtQ_String, amountIn_String, amountOut_String, feeAmount_String := SwapMathComputeSwapStepStr(price, priceTarget, liquidity, amountIn_i256, fee) - - shouldEQ(t, sqrtQ_String, "417332158212080721273783715441581") - shouldEQ(t, amountIn_String, "1") - shouldEQ(t, feeAmount_String, "1") - shouldEQ(t, amountOut_String, "1") -} - -func TestSwapMathComputeSwapStepStr_5(t *testing.T) { - var amountIn_feeAmount *u256.Uint - // target price of 1 uses partial input amount - - price = u256.MustFromDecimal("2") - priceTarget = u256.MustFromDecimal("1") - liquidity = u256.MustFromDecimal("1") - amountIn_i256 = i256.MustFromDecimal("3915081100057732413702495386755767") - fee = 1 - - sqrtQ_String, amountIn_String, amountOut_String, feeAmount_String := SwapMathComputeSwapStepStr(price, priceTarget, liquidity, amountIn_i256, fee) - - shouldEQ(t, sqrtQ_String, "1") - shouldEQ(t, feeAmount_String, "39614120871253040049813") - shouldEQ(t, amountOut_String, "0") - shouldEQ(t, amountIn_String, "39614081257132168796771975168") - amountIn_feeAmount = u256.MustFromDecimal(amountIn_String) - amountIn_feeAmount.Add(amountIn_feeAmount, u256.MustFromDecimal(feeAmount_String)) - - if amountIn_feeAmount.Cmp(u256.MustFromDecimal("3915081100057732413702495386755767")) >= 0 { - t.Errorf("amountIn+feeAmount should be less than or eq to 3915081100057732413702495386755767") - } -} - -func TestSwapMathComputeSwapStepStr_6(t *testing.T) { - // entire input amount taken as fee - price = u256.MustFromDecimal("2413") - priceTarget = u256.MustFromDecimal("79887613182836312") - liquidity = u256.MustFromDecimal("1985041575832132834610021537970") - amountIn_i256 = i256.MustFromDecimal("10") - fee = 1872 - - sqrtQ_String, amountIn_String, amountOut_String, feeAmount_String := SwapMathComputeSwapStepStr(price, priceTarget, liquidity, amountIn_i256, fee) - - shouldEQ(t, amountIn_String, "0") - shouldEQ(t, feeAmount_String, "10") - shouldEQ(t, amountOut_String, "0") - shouldEQ(t, sqrtQ_String, "2413") -} - -func TestSwapMathComputeSwapStepStr_7(t *testing.T) { - // handles intermediate insufficient liquidity in zero for one exact output case - - price = u256.MustFromDecimal("20282409603651670423947251286016") - priceTarget = u256.MulDiv(price, u256.NewUint(11), u256.NewUint(10)) - liquidity = u256.MustFromDecimal("1024") - amountIn_i256 = i256.MustFromDecimal("-4") - fee = 3000 - - sqrtQ_String, amountIn_String, amountOut_String, feeAmount_String := SwapMathComputeSwapStepStr(price, priceTarget, liquidity, amountIn_i256, fee) - - shouldEQ(t, amountOut_String, "0") - shouldEQ(t, sqrtQ_String, priceTarget.ToString()) - shouldEQ(t, amountIn_String, "26215") - shouldEQ(t, feeAmount_String, "79") -} - -func TestSwapMathComputeSwapStepStr_8(t *testing.T) { - // handles intermediate insufficient liquidity in one for zero exact output case - price = u256.MustFromDecimal("20282409603651670423947251286016") - priceTarget = u256.MulDiv(price, u256.NewUint(9), u256.NewUint(10)) - liquidity = u256.MustFromDecimal("1024") - amountIn_i256 = i256.MustFromDecimal("-263000") - fee = 3000 - - sqrtQ_String, amountIn_String, amountOut_String, feeAmount_String := SwapMathComputeSwapStepStr(price, priceTarget, liquidity, amountIn_i256, fee) - - shouldEQ(t, amountOut_String, "26214") - shouldEQ(t, sqrtQ_String, priceTarget.ToString()) - shouldEQ(t, amountIn_String, "1") - shouldEQ(t, feeAmount_String, "1") -} diff --git a/_deploy/p/gnoswap/pool/swap_math.gno b/_deploy/p/gnoswap/pool/swap_math.gno index 6150981f3..81b9c2d28 100644 --- a/_deploy/p/gnoswap/pool/swap_math.gno +++ b/_deploy/p/gnoswap/pool/swap_math.gno @@ -5,14 +5,34 @@ import ( u256 "gno.land/p/gnoswap/uint256" ) +// SwapMathComputeSwapStepStr computes the next sqrt price, amount in, amount out, and fee amount +// Computes the result of swapping some amount in, or amount out, given the parameters of the swap +// The fee, plus the amount in, will never exceed the amount remaining if the swap's `amountSpecified` is positive +// +// input: +// - sqrtRatioCurrentX96: the current sqrt price of the pool +// - sqrtRatioTargetX96: The price that cannot be exceeded, from which the direction of the swap is inferred +// - liquidity: The usable liquidity of the pool +// - amountRemaining: How much input or output amount is remaining to be swapped in/out +// - feePips: The fee taken from the input amount, expressed in hundredths of a bip +// +// output: +// - sqrtRatioNextX96: The price after swapping the amount in/out, not to exceed the price target +// - amountIn: The amount to be swapped in, of either token0 or token1, based on the direction of the swap +// - amountOut: The amount to be received, of either token0 or token1, based on the direction of the swap +// - feeAmount: The amount of input that will be taken as a fee func SwapMathComputeSwapStepStr( - sqrtRatioCurrentX96 *u256.Uint, // uint160 - sqrtRatioTargetX96 *u256.Uint, // uint160 - liquidity *u256.Uint, // uint128 - amountRemaining *i256.Int, // int256 + sqrtRatioCurrentX96 *u256.Uint, + sqrtRatioTargetX96 *u256.Uint, + liquidity *u256.Uint, + amountRemaining *i256.Int, feePips uint64, -) (string, string, string, string) { // (sqrtRatioNextX96, amountIn, amountOut, feeAmount *u256.Uint) - isToken1Expensive := sqrtRatioCurrentX96.Gte(sqrtRatioTargetX96) +) (string, string, string, string) { + if sqrtRatioCurrentX96 == nil || sqrtRatioTargetX96 == nil || liquidity == nil || amountRemaining == nil { + panic("SwapMathComputeSwapStepStr: invalid input") + } + + zeroForOne := sqrtRatioCurrentX96.Gte(sqrtRatioTargetX96) // POSTIVIE == EXACT_IN => Estimated AmountOut // NEGATIVE == EXACT_OUT => Estimated AmountIn @@ -25,75 +45,94 @@ func SwapMathComputeSwapStepStr( if exactIn { amountRemainingLessFee := u256.MulDiv(amountRemaining.Abs(), u256.NewUint(1000000-feePips), u256.NewUint(1000000)) - - if isToken1Expensive { - amountIn = sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, true) + if zeroForOne { + amountIn = sqrtPriceMathGetAmount0DeltaHelper( + sqrtRatioTargetX96.Clone(), + sqrtRatioCurrentX96.Clone(), + liquidity.Clone(), + true) } else { - amountIn = sqrtPriceMathGetAmount1DeltaHelper(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, true) + amountIn = sqrtPriceMathGetAmount1DeltaHelper( + sqrtRatioCurrentX96.Clone(), + sqrtRatioTargetX96.Clone(), + liquidity.Clone(), + true) } if amountRemainingLessFee.Gte(amountIn) { - sqrtRatioNextX96 = sqrtRatioTargetX96 + sqrtRatioNextX96 = sqrtRatioTargetX96.Clone() } else { sqrtRatioNextX96 = sqrtPriceMathGetNextSqrtPriceFromInput( - sqrtRatioCurrentX96, - liquidity, - amountRemainingLessFee, - isToken1Expensive, + sqrtRatioCurrentX96.Clone(), + liquidity.Clone(), + amountRemainingLessFee.Clone(), + zeroForOne, ) } - } else { - if isToken1Expensive { - amountOut = sqrtPriceMathGetAmount1DeltaHelper(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, false) + if zeroForOne { + amountOut = sqrtPriceMathGetAmount1DeltaHelper( + sqrtRatioTargetX96.Clone(), + sqrtRatioCurrentX96.Clone(), + liquidity.Clone(), + false) } else { - amountOut = sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, false) + amountOut = sqrtPriceMathGetAmount0DeltaHelper( + sqrtRatioCurrentX96.Clone(), + sqrtRatioTargetX96.Clone(), + liquidity.Clone(), + false) } if amountRemaining.Abs().Gte(amountOut) { - sqrtRatioNextX96 = sqrtRatioTargetX96 + sqrtRatioNextX96 = sqrtRatioTargetX96.Clone() } else { sqrtRatioNextX96 = sqrtPriceMathGetNextSqrtPriceFromOutput( - sqrtRatioCurrentX96, - liquidity, + sqrtRatioCurrentX96.Clone(), + liquidity.Clone(), amountRemaining.Abs(), - isToken1Expensive, + zeroForOne, ) } } - max := sqrtRatioTargetX96.Eq(sqrtRatioNextX96) + isMax := sqrtRatioTargetX96.Eq(sqrtRatioNextX96) - if isToken1Expensive { - if max && exactIn { - amountIn = amountIn - } else { - amountIn = sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, true) + if zeroForOne { + if !(isMax && exactIn) { + amountIn = sqrtPriceMathGetAmount0DeltaHelper( + sqrtRatioNextX96.Clone(), + sqrtRatioCurrentX96.Clone(), + liquidity.Clone(), + true) } - - if max && !exactIn { - amountOut = amountOut - } else { - amountOut = sqrtPriceMathGetAmount1DeltaHelper(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, false) + if !(isMax && !exactIn) { + amountOut = sqrtPriceMathGetAmount1DeltaHelper( + sqrtRatioNextX96.Clone(), + sqrtRatioCurrentX96.Clone(), + liquidity.Clone(), + false) } } else { - if max && exactIn { - amountIn = amountIn - } else { - amountIn = sqrtPriceMathGetAmount1DeltaHelper(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, true) + if !(isMax && exactIn) { + amountIn = sqrtPriceMathGetAmount1DeltaHelper( + sqrtRatioCurrentX96.Clone(), + sqrtRatioNextX96.Clone(), + liquidity.Clone(), + true) } - - if max && !exactIn { - amountOut = amountOut - } else { - amountOut = sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, false) + if !(isMax && !exactIn) { + amountOut = sqrtPriceMathGetAmount0DeltaHelper( + sqrtRatioCurrentX96.Clone(), + sqrtRatioNextX96.Clone(), + liquidity.Clone(), + false) } } if !exactIn && amountOut.Gt(amountRemaining.Abs()) { amountOut = amountRemaining.Abs() } - if exactIn && !(sqrtRatioNextX96.Eq(sqrtRatioTargetX96)) { feeAmount = new(u256.Uint).Sub(amountRemaining.Abs(), amountIn) } else { diff --git a/_deploy/p/gnoswap/pool/swap_math_test.gno b/_deploy/p/gnoswap/pool/swap_math_test.gno new file mode 100644 index 000000000..505a8e716 --- /dev/null +++ b/_deploy/p/gnoswap/pool/swap_math_test.gno @@ -0,0 +1,273 @@ +package pool + +import ( + "testing" + + "gno.land/p/demo/uassert" + + i256 "gno.land/p/gnoswap/int256" + u256 "gno.land/p/gnoswap/uint256" +) + +func TestSwapMathComputeSwapStepStr(t *testing.T) { + tests := []struct { + name string + currentX96, targetX96 *u256.Uint + liquidity *u256.Uint + amountRemaining *i256.Int + feePips uint64 + sqrtNextX96 *u256.Uint + chkSqrtNextX96 func(sqrtRatioNextX96, priceTarget *u256.Uint) + amountIn, amountOut, feeAmount string + }{ + { + name: "exact amount in that gets capped at price target in one for zero", + currentX96: encodePriceSqrt(t, "1", "1"), + targetX96: encodePriceSqrt(t, "101", "100"), + liquidity: u256.MustFromDecimal("2000000000000000000"), + amountRemaining: i256.MustFromDecimal("1000000000000000000"), + feePips: 600, + sqrtNextX96: encodePriceSqrt(t, "101", "100"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "9975124224178055", + amountOut: "9925619580021728", + feeAmount: "5988667735148", + }, + { + name: "exact amount out that gets capped at price target in one for zero", + currentX96: encodePriceSqrt(t, "1", "1"), + targetX96: encodePriceSqrt(t, "101", "100"), + liquidity: u256.MustFromDecimal("2000000000000000000"), + amountRemaining: i256.MustFromDecimal("-1000000000000000000"), + feePips: 600, + sqrtNextX96: encodePriceSqrt(t, "101", "100"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "9975124224178055", + amountOut: "9925619580021728", + feeAmount: "5988667735148", + }, + { + name: "exact amount in that is fully spent in one for zero", + currentX96: encodePriceSqrt(t, "1", "1"), + targetX96: encodePriceSqrt(t, "1000", "100"), + liquidity: u256.MustFromDecimal("2000000000000000000"), + amountRemaining: i256.MustFromDecimal("1000000000000000000"), + sqrtNextX96: encodePriceSqrt(t, "1000", "100"), + feePips: 600, + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Lte(priceTarget)) + }, + amountIn: "999400000000000000", + amountOut: "666399946655997866", + feeAmount: "600000000000000", + }, + { + name: "exact amount out that is fully received in one for zero", + currentX96: encodePriceSqrt(t, "1", "1"), + targetX96: encodePriceSqrt(t, "1000", "100"), + liquidity: u256.MustFromDecimal("2000000000000000000"), + amountRemaining: i256.MustFromDecimal("-1000000000000000000"), + feePips: 600, + sqrtNextX96: encodePriceSqrt(t, "1000", "100"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Lt(priceTarget)) + }, + amountIn: "2000000000000000000", + amountOut: "1000000000000000000", + feeAmount: "1200720432259356", + }, + { + name: "amount out is capped at the desired amount out", + currentX96: u256.MustFromDecimal("417332158212080721273783715441582"), + targetX96: u256.MustFromDecimal("1452870262520218020823638996"), + liquidity: u256.MustFromDecimal("159344665391607089467575320103"), + amountRemaining: i256.MustFromDecimal("-1"), + feePips: 1, + sqrtNextX96: u256.MustFromDecimal("417332158212080721273783715441581"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "1", + amountOut: "1", + feeAmount: "1", + }, + { + name: "target price of 1 uses partial input amount", + currentX96: u256.MustFromDecimal("2"), + targetX96: u256.MustFromDecimal("1"), + liquidity: u256.MustFromDecimal("1"), + amountRemaining: i256.MustFromDecimal("3915081100057732413702495386755767"), + feePips: 1, + sqrtNextX96: u256.MustFromDecimal("1"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "39614081257132168796771975168", + amountOut: "0", + feeAmount: "39614120871253040049813", + }, + { + name: "entire input amount taken as fee", + currentX96: u256.MustFromDecimal("2413"), + targetX96: u256.MustFromDecimal("79887613182836312"), + liquidity: u256.MustFromDecimal("1985041575832132834610021537970"), + amountRemaining: i256.MustFromDecimal("10"), + feePips: 1872, + sqrtNextX96: u256.MustFromDecimal("2413"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "0", + amountOut: "0", + feeAmount: "10", + }, + { + name: "handles intermediate insufficient liquidity in zero for one exact output case", + currentX96: u256.MustFromDecimal("20282409603651670423947251286016"), + targetX96: u256.MustFromDecimal("22310650564016837466341976414617"), + liquidity: u256.MustFromDecimal("1024"), + amountRemaining: i256.MustFromDecimal("-4"), + feePips: 3000, + sqrtNextX96: u256.MustFromDecimal("22310650564016837466341976414617"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "26215", + amountOut: "0", + feeAmount: "79", + }, + { + name: "handles intermediate insufficient liquidity in one for zero exact output case", + currentX96: u256.MustFromDecimal("20282409603651670423947251286016"), + targetX96: u256.MustFromDecimal("18254168643286503381552526157414"), + liquidity: u256.MustFromDecimal("1024"), + amountRemaining: i256.MustFromDecimal("-263000"), + feePips: 3000, + sqrtNextX96: u256.MustFromDecimal("18254168643286503381552526157414"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "1", + amountOut: "26214", + feeAmount: "1", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + sqrtRatioNextX96, amountIn, amountOut, feeAmount := SwapMathComputeSwapStepStr(test.currentX96, test.targetX96, test.liquidity, test.amountRemaining, test.feePips) + test.chkSqrtNextX96(u256.MustFromDecimal(sqrtRatioNextX96), test.sqrtNextX96) + uassert.Equal(t, amountIn, test.amountIn) + uassert.Equal(t, amountOut, test.amountOut) + uassert.Equal(t, feeAmount, test.feeAmount) + }) + } +} + +func TestSwapMathComputeSwapStepStrFail(t *testing.T) { + tests := []struct { + name string + currentX96, targetX96 *u256.Uint + liquidity *u256.Uint + amountRemaining *i256.Int + feePips uint64 + sqrtNextX96 *u256.Uint + chkSqrtNextX96 func(sqrtRatioNextX96, priceTarget *u256.Uint) + amountIn, amountOut, feeAmount string + shouldPanic bool + expectedMessage string + }{ + { + name: "input parameter is nil", + currentX96: nil, + targetX96: nil, + liquidity: nil, + amountRemaining: nil, + feePips: 600, + sqrtNextX96: encodePriceSqrt(t, "101", "100"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "9975124224178055", + amountOut: "9925619580021728", + feeAmount: "5988667735148", + shouldPanic: true, + expectedMessage: "SwapMathComputeSwapStepStr: invalid input", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + if test.shouldPanic { + if errMsg, ok := r.(string); ok { + uassert.Equal(t, test.expectedMessage, errMsg) + } else { + t.Errorf("expected a panic with message, got: %v", r) + } + } else { + t.Errorf("unexpected panic: %v", r) + } + } else { + if test.shouldPanic { + t.Errorf("expected a panic, but none occurred") + } + } + }() + + SwapMathComputeSwapStepStr( + test.currentX96, + test.targetX96, + test.liquidity, + test.amountRemaining, + test.feePips) + }) + } +} + +// encodePriceSqrt calculates the sqrt((reserve1 << 192) / reserve0) +func encodePriceSqrt(t *testing.T, reserve1, reserve0 string) *u256.Uint { + t.Helper() + + reserve1Uint := u256.MustFromDecimal(reserve1) + reserve0Uint := u256.MustFromDecimal(reserve0) + + if reserve0Uint.IsZero() { + panic("division by zero") + } + + // numerator = reserve1 * (2^192) + two192 := new(u256.Uint).Lsh(u256.NewUint(1), 192) + numerator := new(u256.Uint).Mul(reserve1Uint, two192) + + // ratioX192 = numerator / reserve0 + ratioX192 := new(u256.Uint).Div(numerator, reserve0Uint) + + // Return sqrt(ratioX192) + return sqrt(t, ratioX192) +} + +// sqrt computes the integer square root of a u256.Uint +func sqrt(t *testing.T, x *u256.Uint) *u256.Uint { + t.Helper() + + if x.IsZero() { + return u256.NewUint(0) + } + + z := new(u256.Uint).Set(x) + y := new(u256.Uint).Rsh(z, 1) // Initial guess is x / 2 + + temp := new(u256.Uint) + for y.Cmp(z) < 0 { + z.Set(y) + temp.Div(x, z) + y.Add(z, temp).Rsh(y, 1) + } + return z +}