diff --git a/_deploy/p/gnoswap/pool/__TEST_sqrt_price_math_test.gnoA b/_deploy/p/gnoswap/pool/__TEST_sqrt_price_math_test.gnoA deleted file mode 100644 index b3da6089..00000000 --- a/_deploy/p/gnoswap/pool/__TEST_sqrt_price_math_test.gnoA +++ /dev/null @@ -1,690 +0,0 @@ -package pool - -import ( - "testing" - - "gno.land/r/gnoswap/v1/consts" - - i256 "gno.land/p/gnoswap/int256" - u256 "gno.land/p/gnoswap/uint256" -) - -func TestGetNextSqrtPriceFromInput_1(t *testing.T) { - // fails if price is zero - sqrtPX96 := u256.Zero() - liquidity := u256.Zero() - amountIn := u256.MustFromDecimal("1000000000000000000") // 1e18 - zeroForOne := false - amountIn.Div(amountIn, u256.NewUint(10)) - - shouldPanic( - t, - func() { - sqrtPriceMathGetNextSqrtPriceFromInput(sqrtPX96, liquidity, amountIn, zeroForOne) - }, - ) -} - -func TestGetNextSqrtPriceFromInput_2(t *testing.T) { - // fails if liquidity is zero - sqrtPX96 := u256.One() - liquidity := u256.Zero() - amountIn := u256.MustFromDecimal("1000000000000000000") // 1e18 - zeroForOne := true - amountIn.Div(amountIn, u256.NewUint(10)) - - shouldPanic( - t, - func() { - sqrtPriceMathGetNextSqrtPriceFromInput(sqrtPX96, liquidity, amountIn, zeroForOne) - }, - ) -} - -func TestGetNextSqrtPriceFromInput_3(t *testing.T) { - // fails if input amount overflows the price - sqrtPX96 := u256.NewUint(2) - sqrtPX96.Exp(sqrtPX96, u256.NewUint(160)) - sqrtPX96.Sub(sqrtPX96, u256.One()) - - liquidity := u256.NewUint(1024) - amountIn := u256.NewUint(1024) - zeroForOne := false - - shouldPanic( - t, - func() { - sqrtPriceMathGetNextSqrtPriceFromInput(sqrtPX96, liquidity, amountIn, zeroForOne) - }, - ) -} - -func TestGetNextSqrtPriceFromInput_4(t *testing.T) { - // any input amount cannot underflow the price - sqrtPX96 := u256.One() - liquidity := u256.One() - amountIn := u256.NewUint(2) - amountIn.Exp(amountIn, u256.NewUint(225)) - zeroForOne := true - - expected := u256.One() - - got := sqrtPriceMathGetNextSqrtPriceFromInput(sqrtPX96, liquidity, amountIn, zeroForOne) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("any input amount cannot underflow the price") - } -} - -func TestGetNextSqrtPriceFromInput_5(t *testing.T) { - // returns input price if amount in is zero and zeroForOne := true - sqrtPX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - liquidity := u256.MustFromDecimal("1000000000000000000") - liquidity.Div(liquidity, u256.NewUint(10)) - amountIn := u256.Zero() - zeroForOne := true - - expected := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - - got := sqrtPriceMathGetNextSqrtPriceFromInput(sqrtPX96, liquidity, amountIn, zeroForOne) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("returns input price if amount in is zero and zeroForOne := true") - } -} - -func TestGetNextSqrtPriceFromInput_6(t *testing.T) { - // returns input price if amount in is zero and zeroForOne := false - sqrtPX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - liquidity := u256.MustFromDecimal("1000000000000000000") - liquidity.Div(liquidity, u256.NewUint(10)) - amountIn := u256.Zero() - zeroForOne := false - - expected := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - - got := sqrtPriceMathGetNextSqrtPriceFromInput(sqrtPX96, liquidity, amountIn, zeroForOne) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("returns input price if amount in is zero and zeroForOne := false") - } -} - -func TestGetNextSqrtPriceFromInput_7(t *testing.T) { - var maxAmountNoOverflow *u256.Uint - var a *u256.Uint - - // returns the minimum price for max inputs - sqrtPX96 := u256.NewUint(2) - sqrtPX96.Exp(sqrtPX96, u256.NewUint(160)) - sqrtPX96.Sub(sqrtPX96, u256.NewUint(1)) - - liquidity := u256.MustFromDecimal(consts.MAX_UINT128) - a = u256.MustFromDecimal(consts.MAX_UINT128) - maxAmountNoOverflow = u256.MustFromDecimal(consts.MAX_UINT256) - a.Lsh(a, 96) - a.Div(a, sqrtPX96) - maxAmountNoOverflow.Sub(maxAmountNoOverflow, a) - - zeroForOne := true - - expected := u256.One() - - got := sqrtPriceMathGetNextSqrtPriceFromInput(sqrtPX96, liquidity, maxAmountNoOverflow, zeroForOne) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("returns the minimum price for max inputs") - } -} - -func TestGetNextSqrtPriceFromInput_8(t *testing.T) { - // input amount of 0.1 token1 - sqrtPX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - liquidity := u256.MustFromDecimal("1000000000000000000") - amountIn := u256.MustFromDecimal("1000000000000000000") - amountIn.Div(amountIn, u256.NewUint(10)) - zeroForOne := false - - expected := u256.MustFromDecimal("87150978765690771352898345369") - - got := sqrtPriceMathGetNextSqrtPriceFromInput(sqrtPX96, liquidity, amountIn, zeroForOne) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("input amount of 0.1 token1") - } -} - -func TestGetNextSqrtPriceFromInput_9(t *testing.T) { - // input amount of 0.1 token1 - sqrtPX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - liquidity := u256.MustFromDecimal("1000000000000000000") - amountIn := u256.MustFromDecimal("1000000000000000000") - amountIn.Div(amountIn, u256.NewUint(10)) - zeroForOne := true - - expected := u256.MustFromDecimal("72025602285694852357767227579") - - got := sqrtPriceMathGetNextSqrtPriceFromInput(sqrtPX96, liquidity, amountIn, zeroForOne) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("input amount of 0.1 token1") - } -} - -func TestGetNextSqrtPriceFromInput_10(t *testing.T) { - // amountIn > type(uint96).max and zeroForOne := true - sqrtPX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - liquidity := u256.MustFromDecimal("10000000000000000000") // 10e18 - amountIn := u256.MustFromDecimal("2") - amountIn.Exp(amountIn, u256.NewUint(100)) - zeroForOne := true - - expected := u256.MustFromDecimal("624999999995069620") - - got := sqrtPriceMathGetNextSqrtPriceFromInput(sqrtPX96, liquidity, amountIn, zeroForOne) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("amountIn > type(uint96).max and zeroForOne := true") - } -} - -func TestGetNextSqrtPriceFromInput_11(t *testing.T) { - // can return 1 with enough amountIn and zeroForOne := true - sqrtPX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - liquidity := u256.MustFromDecimal("1") - amountIn := u256.MustFromDecimal(consts.MAX_UINT256) - amountIn.Div(amountIn, u256.NewUint(2)) - zeroForOne := true - - expected := u256.MustFromDecimal("1") - - got := sqrtPriceMathGetNextSqrtPriceFromInput(sqrtPX96, liquidity, amountIn, zeroForOne) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("can return 1 with enough amountIn and zeroForOne := true") - } -} - -func TestGetNextSqrtPriceFromOutput_1(t *testing.T) { - // fails if price is zero - sqrtPX96 := u256.Zero() - liquidity := u256.Zero() - amountOut := u256.MustFromDecimal("1000000000000000000") // 1e18 - amountOut.Div(amountOut, u256.NewUint(10)) - zeroForOne := false - - shouldPanic( - t, - func() { - sqrtPriceMathGetNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne) - }, - ) -} - -func TestGetNextSqrtPriceFromOutput_2(t *testing.T) { - // fails if liquidity is zero - sqrtPX96 := u256.One() - liquidity := u256.Zero() - amountOut := u256.MustFromDecimal("1000000000000000000") // 1e18 - amountOut.Div(amountOut, u256.NewUint(10)) - zeroForOne := true - - shouldPanic( - t, - func() { - sqrtPriceMathGetNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne) - }, - ) -} - -func TestGetNextSqrtPriceFromOutput_3(t *testing.T) { - // fails if output amount is exactly the virtual reserves of token0 - sqrtPX96 := u256.MustFromDecimal("20282409603651670423947251286016") - liquidity := u256.NewUint(1024) - amountOut := u256.MustFromDecimal("4") - zeroForOne := false - - shouldPanic( - t, - func() { - sqrtPriceMathGetNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne) - }, - ) -} - -func TestGetNextSqrtPriceFromOutput_4_1(t *testing.T) { - // fails if output amount is greater than virtual reserves of token0 - sqrtPX96 := u256.MustFromDecimal("20282409603651670423947251286016") - liquidity := u256.NewUint(1024) - amountOut := u256.MustFromDecimal("5") - zeroForOne := false - - shouldPanic( - t, - func() { - sqrtPriceMathGetNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne) - }, - ) -} - -func TestGetNextSqrtPriceFromOutput_4_2(t *testing.T) { - // fails if output amount is greater than virtual reserves of token1 - sqrtPX96 := u256.MustFromDecimal("20282409603651670423947251286016") - liquidity := u256.NewUint(1024) - amountOut := u256.MustFromDecimal("262145") - zeroForOne := true - - shouldPanic( - t, - func() { - sqrtPriceMathGetNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne) - }, - ) -} - -func TestGetNextSqrtPriceFromOutput_5(t *testing.T) { - // fails if output amount is exactly the virtual reserves of token1 - sqrtPX96 := u256.MustFromDecimal("20282409603651670423947251286016") - liquidity := u256.NewUint(1024) - amountOut := u256.MustFromDecimal("262144") - zeroForOne := true - - shouldPanic( - t, - func() { - sqrtPriceMathGetNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne) - }, - ) -} - -func TestGetNextSqrtPriceFromOutput_6(t *testing.T) { - // succeeds if output amount is just less than the virtual reserves of token1 - sqrtPX96 := u256.MustFromDecimal("20282409603651670423947251286016") - liquidity := u256.NewUint(1024) - amountOut := u256.MustFromDecimal("262143") - zeroForOne := true - - expected := u256.MustFromDecimal("77371252455336267181195264") - - got := sqrtPriceMathGetNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("The result should be eq to 77371252455336267181195264") - } -} - -func TestGetNextSqrtPriceFromOutput_7(t *testing.T) { - // puzzling echidna test - sqrtPX96 := u256.MustFromDecimal("20282409603651670423947251286016") - liquidity := u256.NewUint(1024) - amountOut := u256.MustFromDecimal("4") - zeroForOne := false - - shouldPanic( - t, - func() { - sqrtPriceMathGetNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne) - }, - ) -} - -func TestGetNextSqrtPriceFromOutput_8(t *testing.T) { - // returns input price if amount in is zero and zeroForOne := true - sqrtPX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - liquidity := u256.MustFromDecimal("1000000000000000000") - liquidity.Div(liquidity, u256.NewUint(10)) - amountOut := u256.Zero() - zeroForOne := true - - expected := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - - got := sqrtPriceMathGetNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("The result should be eq to 79228162514264337593543950336") - } -} - -func TestGetNextSqrtPriceFromOutput_9(t *testing.T) { - // returns input price if amount in is zero and zeroForOne := false - - sqrtPX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - liquidity := u256.MustFromDecimal("1000000000000000000") - liquidity.Div(liquidity, u256.NewUint(10)) - amountOut := u256.Zero() - zeroForOne := false - expected := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - - got := sqrtPriceMathGetNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("The result should be eq to 79228162514264337593543950336") - } -} - -func TestGetNextSqrtPriceFromOutput_10(t *testing.T) { - // output amount of 0.1 token1 - sqrtPX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - liquidity := u256.MustFromDecimal("1000000000000000000") - amountOut := u256.MustFromDecimal("1000000000000000000") - amountOut.Div(amountOut, u256.NewUint(10)) - zeroForOne := false - expected := u256.MustFromDecimal("88031291682515930659493278152") - - got := sqrtPriceMathGetNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("The result should be eq to 88031291682515930659493278152") - } -} - -func TestGetNextSqrtPriceFromOutput_11(t *testing.T) { - // output amount of 0.1 token1 - sqrtPX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - liquidity := u256.MustFromDecimal("1000000000000000000") - amountOut := u256.MustFromDecimal("1000000000000000000") - amountOut.Div(amountOut, u256.NewUint(10)) - zeroForOne := true - expected := u256.MustFromDecimal("71305346262837903834189555302") - - got := sqrtPriceMathGetNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("The result should be eq to 71305346262837903834189555302") - } -} - -func TestGetNextSqrtPriceFromOutput_12(t *testing.T) { - // reverts if amountOut is impossible in zero for one direction - sqrtPX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - liquidity := u256.One() - amountOut := u256.MustFromDecimal(consts.MAX_UINT256) - zeroForOne := true - - shouldPanic( - t, - func() { - sqrtPriceMathGetNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne) - }, - ) -} - -func TestGetNextSqrtPriceFromOutput_13(t *testing.T) { - // reverts if amountOut is impossible in one for zero direction - sqrtPX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - liquidity := u256.One() - amountOut := u256.MustFromDecimal(consts.MAX_UINT256) - zeroForOne := false - - shouldPanic( - t, - func() { - sqrtPriceMathGetNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut, zeroForOne) - }, - ) -} - -func TestSqrtPriceMathGetAmount0DeltaStr_1(t *testing.T) { - // returns 0 if liquidity is 0 - - sqrtRatioAX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - sqrtRatioBX96 := u256.MustFromDecimal("112045541949572279837463876454") // encodePriceSqrt(2, 1) = 112045541949572279837463876454 - liquidity_i256 := i256.Zero() - - got_string := SqrtPriceMathGetAmount0DeltaStr(sqrtRatioAX96, sqrtRatioBX96, liquidity_i256) - - if got_string != "0" { - t.Errorf("return value should be eq to 0") - } -} - -func TestSqrtPriceMathGetAmount0DeltaHelper_1(t *testing.T) { - // returns 0 if liquidity is 0 - sqrtRatioAX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - sqrtRatioBX96 := u256.MustFromDecimal("112045541949572279837463876454") // encodePriceSqrt(2, 1) = 112045541949572279837463876454 - liquidity := u256.Zero() - roundUp := true - - expected := u256.Zero() - - got := sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp) - - rst := got.Eq(expected) - if !rst { - t.Errorf("return value should be eq to 0") - } -} - -func TestSqrtPriceMathGetAmount0DeltaStr_2(t *testing.T) { - // returns 0 if prices are equal - - sqrtRatioAX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - sqrtRatioBX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - liquidity_i256 := i256.Zero() - - got_string := SqrtPriceMathGetAmount0DeltaStr(sqrtRatioAX96, sqrtRatioBX96, liquidity_i256) - - if got_string != "0" { - t.Errorf("return value should be eq to 0") - } -} - -func TestSqrtPriceMathGetAmount0DeltaStr_3(t *testing.T) { - // return value should be eq to 90909090909090910 - - sqrtRatioAX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - sqrtRatioBX96 := u256.MustFromDecimal("87150978765690771352898345369") // encodePriceSqrt(121, 100) = 87150978765690771352898345369 - liquidity_i256 := i256.MustFromDecimal("1000000000000000000") - - got_string := SqrtPriceMathGetAmount0DeltaStr(sqrtRatioAX96, sqrtRatioBX96, liquidity_i256) - - if got_string != "90909090909090910" { - t.Errorf("return value should be eq to 90909090909090910") - } -} - -func TestSqrtPriceMathGetAmount0DeltaStr_4(t *testing.T) { - // return value should be eq to 90909090909090910 - - sqrtRatioAX96 := u256.MustFromDecimal("2787593149816327892691964784081045188247552") // encodePriceSqrt(BigNumber.from(2).pow(90), 1) = 2787593149816327892691964784081045188247552 - sqrtRatioBX96 := u256.MustFromDecimal("22300745198530623141535718272648361505980416") // encodePriceSqrt(BigNumber.from(2).pow(96), 1) = 22300745198530623141535718272648361505980416 - liquidity_i256 := i256.MustFromDecimal("1000000000000000000") - - got_string := SqrtPriceMathGetAmount0DeltaStr(sqrtRatioAX96, sqrtRatioBX96, liquidity_i256) - if got_string == "0" { - t.Errorf("The result should not return 0") - } -} - -func TestSqrtPriceMathGetAmount0DeltaHelper_2(t *testing.T) { - // returns 0 if prices are equal - - sqrtRatioAX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - sqrtRatioBX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - liquidity := u256.Zero() - roundUp := true - - expected := u256.Zero() - - got := sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp) - - rst := got.Eq(expected) - if !rst { - t.Errorf("return value should be eq to 0") - } -} - -func TestSqrtPriceMathGetAmount0DeltaHelper_3(t *testing.T) { - sqrtRatioAX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - sqrtRatioBX96 := u256.MustFromDecimal("87150978765690771352898345369") // encodePriceSqrt(121, 100) = 79228162514264337593543950336 - liquidity := u256.MustFromDecimal("1000000000000000000") - roundUp := true - - expected := u256.MustFromDecimal("90909090909090910") - - got := sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("The result should be eq to 90909090909090910") - } -} - -func TestSqrtPriceMathGetAmount0DeltaHelper_4(t *testing.T) { - // the sub between the result of roundup and rounddown should be eq to 1 - var got2 *u256.Uint - sqrtRatioAX96 := u256.MustFromDecimal("112045541949572279837463876454") // encodePriceSqrt(2, 1) = 112045541949572279837463876454 - sqrtRatioBX96 := u256.MustFromDecimal("87150978765690771352898345369") // encodePriceSqrt(121, 100) = 87150978765690771352898345369 - liquidity := u256.MustFromDecimal("1000000000000000000") - roundUp := true - - got := sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp) - - roundUp = false - - got2 = sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp) - - got.Sub(got, got2) - - rst := got.Eq(u256.One()) - - if !rst { - t.Errorf("the sub between the result of roundup and rounddown should be eq to 1") - } -} - -func TestSqrtPriceMathGetAmount0DeltaHelper_5(t *testing.T) { - // works for prices that overflow - - var got2 *u256.Uint - sqrtRatioAX96 := u256.MustFromDecimal("2787593149816327892691964784081045188247552") // encodePriceSqrt(BigNumber.from(2).pow(90), 1) = 2787593149816327892691964784081045188247552 - sqrtRatioBX96 := u256.MustFromDecimal("22300745198530623141535718272648361505980416") // encodePriceSqrt(BigNumber.from(2).pow(96), 1) = 22300745198530623141535718272648361505980416 - liquidity := u256.MustFromDecimal("1000000000000000000") - roundUp := true - - got := sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp) - - roundUp = false - - got2 = sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp) - - // println(got.ToString()) - // println(got2.ToString()) - got.Sub(got, got2) - - rst := got.Eq(u256.One()) - - if !rst { - t.Errorf("the sub between the result of roundup and rounddown should be eq to 1") - } -} - -func TestSqrtPriceMathGetAmount1DeltaHelper_1(t *testing.T) { - // returns 0 if liquidity is 0 - - var got2 *u256.Uint - sqrtRatioAX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - sqrtRatioBX96 := u256.MustFromDecimal("112045541949572279837463876454") // encodePriceSqrt(2, 1) = 112045541949572279837463876454 - liquidity := u256.MustFromDecimal("0") - roundUp := true - - rst := sqrtPriceMathGetAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp) - shouldEQ(t, rst.ToString(), "0") -} - -func TestSqrtPriceMathGetAmount1DeltaHelper_2(t *testing.T) { - // returns 0 if prices are equal - - sqrtRatioAX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - sqrtRatioBX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - liquidity := u256.MustFromDecimal("1") - roundUp := true - - expected := u256.Zero() - - got := sqrtPriceMathGetAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("returns 0 if prices are equal") - } -} - -func TestSqrtPriceMathGetAmount1DeltaHelper_3(t *testing.T) { - var got2 *u256.Uint - // returns 0.1 amount1 for price of 1 to 1.21 - sqrtRatioAX96 := u256.MustFromDecimal("79228162514264337593543950336") // encodePriceSqrt(1, 1) = 79228162514264337593543950336 - sqrtRatioBX96 := u256.MustFromDecimal("87150978765690771352898345369") // encodePriceSqrt(121, 100) = 87150978765690771352898345369 - liquidity := u256.MustFromDecimal("1000000000000000000") - roundUp := true - - expected := u256.MustFromDecimal("100000000000000000") // 0.1e18 - - got := sqrtPriceMathGetAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp) - - rst := got.Eq(expected) - - if !rst { - t.Errorf("the result should be eq to expected") - } - roundUp = false - - got2 = sqrtPriceMathGetAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp) - got.Sub(got, got2) - - rst = got.Eq(u256.One()) - if !rst { - t.Errorf("the sub between the result of roundup and rounddown should be eq to 1") - } -} - -func TestSwapComputation(t *testing.T) { - // sqrtP * sqrtQ overflows - - sqrtPX96 := u256.MustFromDecimal("1025574284609383690408304870162715216695788925244") - liquidity := u256.MustFromDecimal("50015962439936049619261659728067971248") - amountIn := u256.NewUint(406) - zeroForOne := true - - expected := u256.MustFromDecimal("1025574284609383582644711336373707553698163132913") - - got := sqrtPriceMathGetNextSqrtPriceFromInput(sqrtPX96, liquidity, amountIn, zeroForOne) - rst := got.Eq(expected) - if !rst { - t.Errorf("The result should eq to expected") - } - - got = sqrtPriceMathGetAmount0DeltaHelper(expected, sqrtPX96, liquidity, true) - rst = got.Eq(u256.NewUint(406)) - if !rst { - t.Errorf("The result should eq to 406") - } -} diff --git a/_deploy/p/gnoswap/pool/consts.gno b/_deploy/p/gnoswap/pool/consts.gno index 17acbe4e..e8b0b33e 100644 --- a/_deploy/p/gnoswap/pool/consts.gno +++ b/_deploy/p/gnoswap/pool/consts.gno @@ -8,6 +8,7 @@ const ( MAX_UINT128 string = "340282366920938463463374607431768211455" MAX_UINT160 string = "1461501637330902918203684832716283019655932542975" MAX_UINT256 string = "115792089237316195423570985008687907853269984665640564039457584007913129639935" + MAX_INT256 string = "57896044618658097711785492504343953926634992332820282019728792003956564819967" Q64 string = "18446744073709551616" // 2 ** 64 Q96 string = "79228162514264337593543950336" // 2 ** 96 diff --git a/_deploy/p/gnoswap/pool/gno.mod b/_deploy/p/gnoswap/pool/gno.mod index 23776be3..ea092a3f 100644 --- a/_deploy/p/gnoswap/pool/gno.mod +++ b/_deploy/p/gnoswap/pool/gno.mod @@ -1,7 +1 @@ module gno.land/p/gnoswap/pool - -require ( - gno.land/p/gnoswap/int256 v0.0.0-latest - gno.land/p/gnoswap/uint256 v0.0.0-latest - gno.land/r/gnoswap/v1/consts v0.0.0-latest -) diff --git a/_deploy/p/gnoswap/pool/sqrt_price_math.gno b/_deploy/p/gnoswap/pool/sqrt_price_math.gno index 175f9224..29b16fea 100644 --- a/_deploy/p/gnoswap/pool/sqrt_price_math.gno +++ b/_deploy/p/gnoswap/pool/sqrt_price_math.gno @@ -5,17 +5,45 @@ import ( u256 "gno.land/p/gnoswap/uint256" ) +var ( + Q96_RESOLUTION = uint(96) + Q160_RESOLUTION = uint(160) +) + +// sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp calculates the next square root price +// based on the amount of token0 added or removed from the pool. +// NOTE: Always rounds up, because in the exact output case (increasing price) we need to move the price at least +// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the +// price less in order to not send too much output. +// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96), +// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount). +// +// Parameters: +// - sqrtPX96: The current square root price as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a Q128 fixed-point number (uint128). +// - amount: The amount of token0 to be added or removed from the pool (uint256). +// - add: A boolean indicating whether the amount of token0 is being added (true) or removed (false). +// +// Returns: +// - The price after adding or removing amount, depending on add +// +// Notes: +// - When `add` is true, the function calculates the new square root price after adding `amount` of token0. +// - When `add` is false, the function calculates the new square root price after removing `amount` of token0. +// - The function uses high-precision math (MulDivRoundingUp, DivRoundingUp) to handle division rounding issues. +// - The function validates input conditions and panics if the state is invalid. func sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp( - sqrtPX96 *u256.Uint, // uint160 - liquidity *u256.Uint, // uint128 - amount *u256.Uint, // uint256 + sqrtPX96 *u256.Uint, + liquidity *u256.Uint, + amount *u256.Uint, add bool, -) *u256.Uint { // uint160 +) *u256.Uint { + // we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price if amount.IsZero() { return sqrtPX96 } - numerator1 := new(u256.Uint).Lsh(liquidity, 96) + numerator1 := new(u256.Uint).Lsh(liquidity, Q96_RESOLUTION) product := new(u256.Uint).Mul(amount, sqrtPX96) if add { @@ -27,22 +55,48 @@ func sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp( } } - div := new(u256.Uint).Div(numerator1, sqrtPX96) - add := new(u256.Uint).Add(div, amount) - return u256.DivRoundingUp(numerator1, add) + divValue := new(u256.Uint).Div(numerator1, sqrtPX96) + addValue := new(u256.Uint).Add(divValue, amount) + return u256.DivRoundingUp(numerator1, addValue) } else { cond1 := new(u256.Uint).Div(product, amount).Eq(sqrtPX96) cond2 := numerator1.Gt(product) if !(cond1 && cond2) { - panic("pool_sqrt price math #1") + panic("invalid pool sqrt price calculation: product/amount != sqrtPX96 or numerator1 <= product") } denominator := new(u256.Uint).Sub(numerator1, product) - return u256.MulDivRoundingUp(numerator1, sqrtPX96, denominator) + nextSqrtPrice := u256.MulDivRoundingUp(numerator1, sqrtPX96, denominator) + max160 := u256.MustFromDecimal(MAX_UINT160) + if nextSqrtPrice.Gt(max160) { + panic("nextSqrtPrice overflows uint160") + } + return nextSqrtPrice } } +// sqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown calculates the next square root price +// based on the amount of token1 added or removed from the pool, with rounding down. +// NOTE: Always rounds down, because in the exact output case (decreasing price) we need to move the price at least +// far enough to get the desired output amount, and in the exact input case (increasing price) we need to move the +// price less in order to not send too much output. +// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity +// +// Parameters: +// - sqrtPX96: The current square root price as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a Q128 fixed-point number (uint128). +// - amount: The amount of token1 to be added or removed from the pool (uint256). +// - add: A boolean indicating whether the amount of token1 is being added (true) or removed (false). +// +// Returns: +// - The next square root price as a Q96 fixed-point number (uint160). +// +// Notes: +// - When `add` is true, the function calculates the new square root price after adding `amount` of token1. +// - When `add` is false, the function calculates the new square root price after removing `amount` of token1. +// - The function uses high-precision math (MulDiv and DivRoundingUp) to handle division and prevent precision loss. +// - The function validates input conditions and panics if the state is invalid. func sqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown( sqrtPX96 *u256.Uint, // uint160 liquidity *u256.Uint, // uint1288 @@ -50,151 +104,295 @@ func sqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown( add bool, ) *u256.Uint { // uint160 quotient := u256.Zero() + max160 := u256.MustFromDecimal(MAX_UINT160) + // if we're adding (subtracting), rounding down requires rounding the quotient down (up) + // in both cases, avoid a mulDiv for most inputs if add { if amount.Lte(u256.MustFromDecimal(MAX_UINT160)) { - value1 := new(u256.Uint).Lsh(amount, 96) - quotient = new(u256.Uint).Div(value1, liquidity) + value := new(u256.Uint).Lsh(amount, Q96_RESOLUTION) + quotient = new(u256.Uint).Div(value, liquidity) } else { quotient = u256.MulDiv(amount, u256.MustFromDecimal(Q96), liquidity) } res := new(u256.Uint).Add(sqrtPX96, quotient) - max160 := u256.MustFromDecimal("1461501637330902918203684832716283019655932542975") - if res.Gt(max160) { - panic("sqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown sqrtPx96 + quotient overflow uint160") + panic("sqrtPx96 + quotient overflow uint160") } return res - } else { if amount.Lte(u256.MustFromDecimal(MAX_UINT160)) { - value1 := new(u256.Uint).Lsh(amount, 96) - quotient = u256.DivRoundingUp(value1, liquidity) + value := new(u256.Uint).Lsh(amount, Q96_RESOLUTION) + quotient = u256.DivRoundingUp(value, liquidity) } else { quotient = u256.MulDivRoundingUp(amount, u256.MustFromDecimal(Q96), liquidity) } if !(sqrtPX96.Gt(quotient)) { - panic("pool_sqrt price math #2") + panic("sqrt price exceeds calculated quotient") } - return new(u256.Uint).Sub(sqrtPX96, quotient) + res := new(u256.Uint).Sub(sqrtPX96, quotient) + if res.Gt(max160) { + mask := new(u256.Uint).Lsh(u256.One(), Q160_RESOLUTION) + mask = mask.Sub(mask, u256.One()) + res = res.And(res, mask) + } + return res } } +// sqrtPriceMathGetNextSqrtPriceFromInput calculates the next square root price +// based on the amount of token0 or token1 added to the pool. +// NOTE: Always rounds up, because in the exact output case (increasing price) we need to move the price at least +// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the +// price less in order to not send too much output. +// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96), +// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount). +// +// Parameters: +// - sqrtPX96: The current square root price as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a Q128 fixed-point number (uint128). +// - amountIn: The amount of token0 or token1 to be added to the pool (uint256). +// - zeroForOne: A boolean indicating whether the amount is being added to token0 (true) or token1 (false). +// +// Returns: +// - The price after adding amountIn, depending on zeroForOne func sqrtPriceMathGetNextSqrtPriceFromInput( - sqrtPX96 *u256.Uint, // uint160 - liquidity *u256.Uint, // uint128 - amountIn *u256.Uint, // uint256 - zeroForOne bool, // bool -) *u256.Uint { // uint160 + sqrtPX96 *u256.Uint, + liquidity *u256.Uint, + amountIn *u256.Uint, + zeroForOne bool, +) *u256.Uint { if sqrtPX96.IsZero() { panic("sqrtPX96 should not be zero") } if liquidity.IsZero() { - panic("pool_sqrtPriceMathGetNextSqrtPriceFromInput_liquidity should not be zero") + panic("liquidity should not be zero") } if zeroForOne { return sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true) + } else { + return sqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true) } - return sqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true) } +// sqrtPriceMathGetNextSqrtPriceFromOutput calculates the next square root price +// based on the amount of token0 or token1 removed from the pool. +// +// NOTE: +// - For zeroForOne == true (Token0 -> Token1): The calculation uses rounding down. +// - For zeroForOne == false (Token1 -> Token0): The calculation uses rounding up. +// +// The most precise formula for this is: +// - liquidity * sqrtPX96 / (liquidity ± amount * sqrtPX96) +// If overflow occurs, it falls back to: +// - liquidity / (liquidity / sqrtPX96 ± amount) +// +// Parameters: +// - sqrtPX96: The current square root price as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a Q128 fixed-point number (uint128). +// - amountOut: The amount of token0 or token1 to be removed from the pool (uint256). +// - zeroForOne: A boolean indicating whether the amount is being removed from token0 (true) or token1 (false). +// +// Returns: +// - The price after removing amountOut, depending on zeroForOne. +// +// Notes: +// - Rounding direction depends on the swap direction (zeroForOne). +// - Relies on helper functions: +// - `sqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown` for Token0 -> Token1. +// - `sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp` for Token1 -> Token0. func sqrtPriceMathGetNextSqrtPriceFromOutput( - sqrtPX96 *u256.Uint, // uint160 - liquidity *u256.Uint, // uint128 - amountOut *u256.Uint, // uint256 - zeroForOne bool, // bool -) *u256.Uint { // uint160 + sqrtPX96 *u256.Uint, + liquidity *u256.Uint, + amountOut *u256.Uint, + zeroForOne bool, +) *u256.Uint { if sqrtPX96.IsZero() { - panic("pool_sqrtPriceMathGetNextSqrtPriceFromOutput_sqrtPX96 should not be zero") + panic("sqrtPX96 should not be zero") } if liquidity.IsZero() { - panic("pool_sqrtPriceMathGetNextSqrtPriceFromOutput_liquidity should not be zero") + panic("liquidity should not be zero") } if zeroForOne { return sqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false) + } else { + return sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false) } - - return sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false) } +// sqrtPriceMathGetAmount0DeltaHelper calculates the absolute difference between the amounts of token0 in two +// liquidity ranges defined by the square root prices sqrtRatioAX96 and sqrtRatioBX96. The difference is +// calculated relative to the range [sqrtRatioAX96, sqrtRatioBX96]. +// +// If sqrtRatioAX96 > sqrtRatioBX96, their values are swapped to ensure sqrtRatioAX96 is the lower bound. +// +// Parameters: +// - sqrtRatioAX96: The lower bound of the range as a Q96 fixed-point number (uint160). +// - sqrtRatioBX96: The upper bound of the range as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a Q128 fixed-point number (uint128). +// - roundUp: A boolean indicating whether the result should be rounded up (true) or down (false). +// +// Returns: +// - The absolute difference between the amounts of token0 in the two ranges as a uint256. +// +// Notes: +// - If sqrtRatioAX96 is zero or negative, the function panics. +// - The result is calculated using high-precision fixed-point arithmetic. +// - Rounding is applied based on the roundUp parameter. func sqrtPriceMathGetAmount0DeltaHelper( - sqrtRatioAX96 *u256.Uint, // uint160 - sqrtRatioBX96 *u256.Uint, // uint160 - liquidity *u256.Uint, // uint160 + sqrtRatioAX96 *u256.Uint, + sqrtRatioBX96 *u256.Uint, + liquidity *u256.Uint, roundUp bool, -) *u256.Uint { // uint256 +) *u256.Uint { if sqrtRatioAX96.Gt(sqrtRatioBX96) { sqrtRatioAX96, sqrtRatioBX96 = sqrtRatioBX96, sqrtRatioAX96 } - numerator1 := new(u256.Uint).Lsh(liquidity, 96) + numerator1 := new(u256.Uint).Lsh(liquidity, Q96_RESOLUTION) numerator2 := new(u256.Uint).Sub(sqrtRatioBX96, sqrtRatioAX96) if !(sqrtRatioAX96.Gt(u256.Zero())) { - panic("pool_sqrt price math #3") + panic("sqrtRatioAX96 must be greater than zero") } if roundUp { - value1 := u256.MulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96) - return u256.DivRoundingUp(value1, sqrtRatioAX96) + value := u256.MulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96) + return u256.DivRoundingUp(value, sqrtRatioAX96) } else { - value1 := u256.MulDiv(numerator1, numerator2, sqrtRatioBX96) - return new(u256.Uint).Div(value1, sqrtRatioAX96) + value := u256.MulDiv(numerator1, numerator2, sqrtRatioBX96) + return new(u256.Uint).Div(value, sqrtRatioAX96) } } +// sqrtPriceMathGetAmount1DeltaHelper calculates the absolute difference between the amounts of token1 in two +// liquidity ranges defined by the square root prices sqrtRatioAX96 and sqrtRatioBX96. The difference is +// calculated relative to the range [sqrtRatioAX96, sqrtRatioBX96]. +// +// If sqrtRatioAX96 > sqrtRatioBX96, their values are swapped to ensure sqrtRatioAX96 is the lower bound. +// +// Parameters: +// - sqrtRatioAX96: The lower bound of the range as a Q96 fixed-point number (uint160). +// - sqrtRatioBX96: The upper bound of the range as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a Q128 fixed-point number (uint128). +// - roundUp: A boolean indicating whether the result should be rounded up (true) or down (false). +// +// Returns: +// - The absolute difference between the amounts of token1 in the two ranges as a uint256. +// +// Notes: +// - Rounding is applied based on the roundUp parameter. +// - The function swaps sqrtRatioAX96 and sqrtRatioBX96 if sqrtRatioAX96 > sqrtRatioBX96. func sqrtPriceMathGetAmount1DeltaHelper( - sqrtRatioAX96 *u256.Uint, // uint160 - sqrtRatioBX96 *u256.Uint, // uint160 - liquidity *u256.Uint, // uint160 + sqrtRatioAX96 *u256.Uint, + sqrtRatioBX96 *u256.Uint, + liquidity *u256.Uint, roundUp bool, -) *u256.Uint { // uint256 +) *u256.Uint { if sqrtRatioAX96.Gt(sqrtRatioBX96) { sqrtRatioAX96, sqrtRatioBX96 = sqrtRatioBX96, sqrtRatioAX96 } + diff := new(u256.Uint).Sub(sqrtRatioBX96, sqrtRatioAX96) if roundUp { - diff := new(u256.Uint).Sub(sqrtRatioBX96, sqrtRatioAX96) return u256.MulDivRoundingUp(liquidity, diff, u256.MustFromDecimal(Q96)) } else { - diff := new(u256.Uint).Sub(sqrtRatioBX96, sqrtRatioAX96) return u256.MulDiv(liquidity, diff, u256.MustFromDecimal(Q96)) } } +// SqrtPriceMathGetAmount0DeltaStr calculates the difference in the amount of token0 +// within a specified liquidity range defined by two square root prices (sqrtRatioAX96 and sqrtRatioBX96). +// This function returns the result as a string representation of an int256 value. +// +// If the liquidity is negative, the result is also negative. +// +// Parameters: +// - sqrtRatioAX96: The lower bound of the range as a Q96 fixed-point number (uint160). +// - sqrtRatioBX96: The upper bound of the range as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a signed Q128 fixed-point number (int128). +// +// Returns: +// - A string representation of the int256 value representing the difference in token0 amounts +// within the specified range. The value is negative if the liquidity is negative. +// +// Notes: +// - This function relies on the helper function `sqrtPriceMathGetAmount0DeltaHelper` to perform the core calculation. +// - The helper function calculates the absolute difference between token0 amounts within the range. +// - If the computed result exceeds the maximum allowable value for int256 (2**255 - 1), the function will panic +// with an appropriate overflow error. +// - The rounding behavior of the result is controlled by the `roundUp` parameter passed to the helper function: +// - For negative liquidity, rounding is always down. +// - For positive liquidity, rounding is always up. func SqrtPriceMathGetAmount0DeltaStr( - sqrtRatioAX96 *u256.Uint, // uint160 - sqrtRatioBX96 *u256.Uint, // uint160 - liquidity *i256.Int, // int128 -) string { // int256 + sqrtRatioAX96 *u256.Uint, + sqrtRatioBX96 *u256.Uint, + liquidity *i256.Int, +) string { if liquidity.IsNeg() { u := sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), false) + if u.Gt(u256.MustFromDecimal(MAX_INT256)) { + // if u > (2**255 - 1), cannot cast to int256 + panic("SqrtPriceMathGetAmount0DeltaStr: overflow") + } i := i256.FromUint256(u) return i256.Zero().Neg(i).ToString() + } else { + u := sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), true) + if u.Gt(u256.MustFromDecimal(MAX_INT256)) { + // if u > (2**255 - 1), cannot cast to int256 + panic("SqrtPriceMathGetAmount0DeltaStr: overflow") + } + return i256.FromUint256(u).ToString() } - - u := sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), true) - return i256.FromUint256(u).ToString() } +// SqrtPriceMathGetAmount1DeltaStr calculates the difference in the amount of token1 +// within a specified liquidity range defined by two square root prices (sqrtRatioAX96 and sqrtRatioBX96). +// This function returns the result as a string representation of an int256 value. +// +// If the liquidity is negative, the result is also negative. +// +// Parameters: +// - sqrtRatioAX96: The lower bound of the range as a Q96 fixed-point number (uint160). +// - sqrtRatioBX96: The upper bound of the range as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a signed Q128 fixed-point number (int128). +// +// Returns: +// - A string representation of the int256 value representing the difference in token1 amounts +// within the specified range. The value is negative if the liquidity is negative. +// +// Notes: +// - This function relies on the helper function `sqrtPriceMathGetAmount1DeltaHelper` to perform the core calculation. +// - The rounding behavior of the result is controlled by the `roundUp` parameter passed to the helper function: +// - For negative liquidity, rounding is always down. +// - For positive liquidity, rounding is always up. func SqrtPriceMathGetAmount1DeltaStr( - sqrtRatioAX96 *u256.Uint, // uint160 - sqrtRatioBX96 *u256.Uint, // uint160 - liquidity *i256.Int, // int128 -) string { // int256 + sqrtRatioAX96 *u256.Uint, + sqrtRatioBX96 *u256.Uint, + liquidity *i256.Int, +) string { if liquidity.IsNeg() { u := sqrtPriceMathGetAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), false) + if u.Gt(u256.MustFromDecimal(MAX_INT256)) { + // if u > (2**255 - 1), cannot cast to int256 + panic("SqrtPriceMathGetAmount1DeltaStr: overflow") + } i := i256.FromUint256(u) return i256.Zero().Neg(i).ToString() + } else { + u := sqrtPriceMathGetAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), true) + if u.Gt(u256.MustFromDecimal(MAX_INT256)) { + // if u > (2**255 - 1), cannot cast to int256 + panic("SqrtPriceMathGetAmount1DeltaStr: overflow") + } + return i256.FromUint256(u).ToString() } - - u := sqrtPriceMathGetAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), true) - return i256.FromUint256(u).ToString() } diff --git a/_deploy/p/gnoswap/pool/sqrt_price_math_test.gno b/_deploy/p/gnoswap/pool/sqrt_price_math_test.gno new file mode 100644 index 00000000..24f450d5 --- /dev/null +++ b/_deploy/p/gnoswap/pool/sqrt_price_math_test.gno @@ -0,0 +1,643 @@ +package pool + +import ( + "testing" + + "gno.land/p/demo/uassert" + + i256 "gno.land/p/gnoswap/int256" + u256 "gno.land/p/gnoswap/uint256" +) + +func TestSqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp(t *testing.T) { + t.Run("zero amount returns same price", func(t *testing.T) { + sqrtPX96 := u256.MustFromDecimal("1000000") + liquidity := u256.MustFromDecimal("2000000") + amount := u256.Zero() + + result := sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp( + sqrtPX96, + liquidity, + amount, + true, + ) + + if !result.Eq(sqrtPX96) { + t.Errorf("Expected %s, got %s", sqrtPX96.ToString(), result.ToString()) + } + }) + + t.Run("remove token0", func(t *testing.T) { + sqrtPX96 := u256.MustFromDecimal("1000000") + liquidity := u256.MustFromDecimal("2000000") + amount := u256.MustFromDecimal("500000") + + result := sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp( + sqrtPX96, + liquidity, + amount, + false, + ) + + if result.Lte(sqrtPX96) { + t.Error("Price should increase when removing token0") + } + }) +} + +func TestSqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown(t *testing.T) { + t.Run("add token1 small amount", func(t *testing.T) { + sqrtPX96 := u256.MustFromDecimal("1000000") + liquidity := u256.MustFromDecimal("2000000") + amount := u256.MustFromDecimal("100000") + + result := sqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown( + sqrtPX96, + liquidity, + amount, + true, + ) + + if result.Lte(sqrtPX96) { + t.Error("Price should increase when adding token1") + } + }) +} + +func TestSqrtPriceMathGetAmount0DeltaStr(t *testing.T) { + t.Run("positive liquidity", func(t *testing.T) { + ratioA := u256.MustFromDecimal("1000000") + ratioB := u256.MustFromDecimal("2000000") + liquidity := i256.FromUint256(u256.MustFromDecimal("5000000")) + + result := SqrtPriceMathGetAmount0DeltaStr(ratioA, ratioB, liquidity) + + if result[0] == '-' { + t.Error("Result should be positive for positive liquidity") + } + }) + + t.Run("negative liquidity", func(t *testing.T) { + ratioA := u256.MustFromDecimal("1000000") + ratioB := u256.MustFromDecimal("2000000") + liquidity := i256.Zero().Neg(i256.FromUint256(u256.MustFromDecimal("5000000"))) + + result := SqrtPriceMathGetAmount0DeltaStr(ratioA, ratioB, liquidity) + + if result[0] != '-' { + t.Error("Result should be negative for negative liquidity") + } + }) + + t.Run("panic overflow when getting amount0 with positive liquidity", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for overflow amount0") + } else { + uassert.Equal(t, "SqrtPriceMathGetAmount0DeltaStr: overflow", r) + } + }() + + // Inputs to trigger panic + sqrtRatioAX96 := u256.MustFromDecimal("1") // very low value + sqrtRatioBX96 := u256.MustFromDecimal("340282366920938463463374607431768211455") // very high value(2^128-1) + liquidity := i256.FromUint256(u256.MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935")) + + SqrtPriceMathGetAmount0DeltaStr(sqrtRatioAX96, sqrtRatioBX96, liquidity) + }) + + t.Run("panic overflow when getting amount0 with negative liquidity", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for overflow amount0") + } else { + uassert.Equal(t, "SqrtPriceMathGetAmount0DeltaStr: overflow", r) + } + }() + + // Inputs to trigger panic + sqrtRatioAX96 := u256.MustFromDecimal("1") // very low value + sqrtRatioBX96 := u256.MustFromDecimal("340282366920938463463374607431768211455") // very high value(2^128-1) + liquidity := i256.FromUint256(u256.MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935")) + liquidity = liquidity.Neg(liquidity) // Make liquidity negative + + SqrtPriceMathGetAmount0DeltaStr(sqrtRatioAX96, sqrtRatioBX96, liquidity) + }) +} + +func TestSqrtPriceMathGetAmount1DeltaStr(t *testing.T) { + t.Run("positive liquidity", func(t *testing.T) { + ratioA := u256.MustFromDecimal("1000000") + ratioB := u256.MustFromDecimal("2000000") + liquidity := i256.FromUint256(u256.MustFromDecimal("5000000")) + + result := SqrtPriceMathGetAmount1DeltaStr(ratioA, ratioB, liquidity) + + if result[0] == '-' { + t.Error("Result should be positive for positive liquidity") + } + }) + + t.Run("negative liquidity", func(t *testing.T) { + ratioA := u256.MustFromDecimal("1000000") + ratioB := u256.MustFromDecimal("2000000") + liquidity := i256.Zero().Neg(i256.FromUint256(u256.MustFromDecimal("5000000"))) + + result := SqrtPriceMathGetAmount0DeltaStr(ratioA, ratioB, liquidity) + + if result[0] != '-' { + t.Error("Result should be negative for negative liquidity") + } + }) + + t.Run("panic overflow when getting amount1 with positive liquidity", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for overflow amount1") + } else { + uassert.Equal(t, "SqrtPriceMathGetAmount1DeltaStr: overflow", r) + } + }() + + // Inputs to trigger panic + sqrtRatioAX96 := u256.MustFromDecimal("1") // very low value + sqrtRatioBX96 := u256.MustFromDecimal("79228162514264337593543950335") // slightly below Q96 + liquidity := i256.FromUint256(u256.MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935")) + + SqrtPriceMathGetAmount1DeltaStr(sqrtRatioAX96, sqrtRatioBX96, liquidity) + }) + + t.Run("panic overflow when getting amount1 with negative liquidity", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for overflow amount1") + } else { + uassert.Equal(t, "SqrtPriceMathGetAmount1DeltaStr: overflow", r) + } + }() + + // Inputs to trigger panic + sqrtRatioAX96 := u256.MustFromDecimal("1") // very low value + sqrtRatioBX96 := u256.MustFromDecimal("79228162514264337593543950335") // slightly below Q96 + liquidity := i256.FromUint256(u256.MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935")) + liquidity = liquidity.Neg(liquidity) // Make liquidity negative + + SqrtPriceMathGetAmount1DeltaStr(sqrtRatioAX96, sqrtRatioBX96, liquidity) + }) +} + +func TestSqrtPriceMathGetNextSqrtPriceFromInput(t *testing.T) { + tests := []struct { + name string + sqrtPriceX96 *u256.Uint + liquidity *u256.Uint + amountIn *u256.Uint + zeroForOne bool + shouldPanic bool + panicMsg string + expectedSqrtPriceX96 string + }{ + { + name: "fails if price is zero", + sqrtPriceX96: u256.Zero(), + liquidity: u256.Zero(), + amountIn: u256.MustFromDecimal("100000000000000000"), + zeroForOne: false, + shouldPanic: true, + panicMsg: "sqrtPX96 should not be zero", + }, + { + name: "fails if liquidity is zero", + sqrtPriceX96: u256.One(), + liquidity: u256.Zero(), + amountIn: u256.MustFromDecimal("100000000000000000"), + zeroForOne: true, + shouldPanic: true, + panicMsg: "liquidity should not be zero", + }, + { + name: "fails if input amount overflows the price", + sqrtPriceX96: u256.MustFromDecimal("1461501637330902918203684832716283019655932542975"), // 2^160 - 1 + liquidity: u256.MustFromDecimal("1024"), + amountIn: u256.MustFromDecimal("1024"), + zeroForOne: false, + shouldPanic: true, + panicMsg: "sqrtPx96 + quotient overflow uint160", + }, + { + name: "any input amount cannot underflow the price", + sqrtPriceX96: u256.MustFromDecimal("1"), + liquidity: u256.MustFromDecimal("1"), + amountIn: u256.MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819968"), // 2^255 + zeroForOne: true, + expectedSqrtPriceX96: "1", + }, + { + name: "returns input price if amount in is zero and zeroForOne = true", + sqrtPriceX96: u256.MustFromDecimal("79228162514264337593543950336"), + liquidity: u256.MustFromDecimal("100000000000000000"), + amountIn: u256.Zero(), + zeroForOne: true, + expectedSqrtPriceX96: "79228162514264337593543950336", + }, + { + name: "returns input price if amount in is zero and zeroForOne = false", + sqrtPriceX96: u256.MustFromDecimal("79228162514264337593543950336"), + liquidity: u256.MustFromDecimal("100000000000000000"), + amountIn: u256.Zero(), + zeroForOne: false, + expectedSqrtPriceX96: "79228162514264337593543950336", + }, + { + name: "returns the minimum price for max inputs", + sqrtPriceX96: u256.MustFromDecimal("1461501637330902918203684832716283019655932542975"), // 2^160 - 1 + liquidity: u256.MustFromDecimal("340282366920938463463374607431768211455"), // 2^128 - 1 + amountIn: u256.MustFromDecimal("115792089237316195423570985008687907853269984665640564039439137263839420088320"), + zeroForOne: true, + expectedSqrtPriceX96: "1", + }, + { + name: "input amount of 0.1 token1", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + amountIn: u256.MustFromDecimal("100000000000000000"), + zeroForOne: false, + expectedSqrtPriceX96: "87150978765690771352898345369", + }, + { + name: "input amount of 0.1 token0", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + amountIn: u256.MustFromDecimal("100000000000000000"), + zeroForOne: true, + expectedSqrtPriceX96: "72025602285694852357767227579", + }, + { + name: "amountIn > type(uint96).max and zeroForOne = true", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.MustFromDecimal("10000000000000000000"), + amountIn: u256.MustFromDecimal("1267650600228229401496703205376"), // 2^128 - 1 + zeroForOne: true, + expectedSqrtPriceX96: "624999999995069620", + }, + { + name: "can return 1 with enough amountIn and zeroForOne = true", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.One(), + amountIn: u256.MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819967"), + zeroForOne: true, + expectedSqrtPriceX96: "1", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + sqrtPriceMathGetNextSqrtPriceFromInput(tt.sqrtPriceX96, tt.liquidity, tt.amountIn, tt.zeroForOne) + }) + } else { + actual := sqrtPriceMathGetNextSqrtPriceFromInput(tt.sqrtPriceX96, tt.liquidity, tt.amountIn, tt.zeroForOne) + uassert.Equal(t, tt.expectedSqrtPriceX96, actual.ToString()) + } + }) + } +} + +func TestSqrtPriceMathGetNextSqrtPriceFromInput2(t *testing.T) { + t.Run("zero sqrtPX96 should panic", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for zero sqrtPX96") + } + }() + + sqrtPriceMathGetNextSqrtPriceFromInput( + u256.Zero(), + u256.MustFromDecimal("1000000"), + u256.MustFromDecimal("500000"), + true, + ) + }) + + t.Run("zero liquidity should panic", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for zero liquidity") + } + }() + + sqrtPriceMathGetNextSqrtPriceFromInput( + u256.MustFromDecimal("1000000"), + u256.Zero(), + u256.MustFromDecimal("500000"), + true, + ) + }) +} + +func TestSqrtPriceMathGetNextSqrtPriceFromOutput(t *testing.T) { + tests := []struct { + name string + sqrtPriceX96 *u256.Uint + liquidity *u256.Uint + amountOut *u256.Uint + zeroForOne bool + shouldPanic bool + expectedSqrtPriceX96 string + }{ + { + name: "fails if price is zero", + sqrtPriceX96: u256.Zero(), + liquidity: u256.Zero(), + amountOut: u256.MustFromDecimal("100000000000000000"), + zeroForOne: false, + shouldPanic: true, + }, + { + name: "fails if liquidity is zero", + sqrtPriceX96: u256.One(), + liquidity: u256.Zero(), + amountOut: u256.MustFromDecimal("100000000000000000"), + zeroForOne: true, + shouldPanic: true, + }, + { + name: "fails if output amount is exactly the virtual reserves of token0", + sqrtPriceX96: u256.MustFromDecimal("20282409603651670423947251286016"), + liquidity: u256.MustFromDecimal("1024"), + amountOut: u256.NewUint(4), + zeroForOne: false, + shouldPanic: true, + }, + { + name: "fails if output amount is greater than virtual reserves of token0", + sqrtPriceX96: u256.MustFromDecimal("20282409603651670423947251286016"), + liquidity: u256.MustFromDecimal("1024"), + amountOut: u256.NewUint(5), + zeroForOne: false, + shouldPanic: true, + }, + { + name: "fails if output amount is greater than virtual reserves of token1", + sqrtPriceX96: u256.MustFromDecimal("20282409603651670423947251286016"), + liquidity: u256.MustFromDecimal("1024"), + amountOut: u256.NewUint(262145), + zeroForOne: true, + shouldPanic: true, + }, + { + name: "fails if output amount is exactly the virtual reserves of token1", + sqrtPriceX96: u256.MustFromDecimal("20282409603651670423947251286016"), + liquidity: u256.MustFromDecimal("1024"), + amountOut: u256.NewUint(262144), + zeroForOne: true, + shouldPanic: true, + }, + { + name: "succeeds if output amount is just less than the virtual reserves of token1", + sqrtPriceX96: u256.MustFromDecimal("20282409603651670423947251286016"), + liquidity: u256.MustFromDecimal("1024"), + amountOut: u256.NewUint(262143), + zeroForOne: true, + expectedSqrtPriceX96: "77371252455336267181195264", + }, + { + name: "puzzling echidna test", + sqrtPriceX96: u256.MustFromDecimal("20282409603651670423947251286016"), + liquidity: u256.MustFromDecimal("1024"), + amountOut: u256.NewUint(4), + zeroForOne: false, + shouldPanic: true, + }, + { + name: "returns input price if amount in is zero and zeroForOne = true", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.MustFromDecimal("100000000000000000"), + amountOut: u256.Zero(), + zeroForOne: true, + expectedSqrtPriceX96: encodePriceSqrt("1", "1").ToString(), + }, + { + name: "returns input price if amount in is zero and zeroForOne = false", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.MustFromDecimal("100000000000000000"), + amountOut: u256.Zero(), + zeroForOne: false, + expectedSqrtPriceX96: encodePriceSqrt("1", "1").ToString(), + }, + { + name: "output amount of 0.1 token1, zeroForOne = false", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + amountOut: u256.MustFromDecimal("100000000000000000"), + zeroForOne: false, + expectedSqrtPriceX96: "88031291682515930659493278152", + }, + { + name: "output amount of 0.1 token1, zeroForOne = true", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + amountOut: u256.MustFromDecimal("100000000000000000"), + zeroForOne: true, + expectedSqrtPriceX96: "71305346262837903834189555302", + }, + { + name: "reverts if amountOut is impossible in zero for one direction", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.NewUint(1), + amountOut: u256.MustFromDecimal(MAX_UINT256), + zeroForOne: true, + shouldPanic: true, + }, + { + name: "reverts if amountOut is impossible in one for zero direction", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.NewUint(1), + amountOut: u256.MustFromDecimal(MAX_UINT256), + zeroForOne: false, + shouldPanic: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic for %s", tt.name) + } + }() + sqrtPriceMathGetNextSqrtPriceFromOutput(tt.sqrtPriceX96, tt.liquidity, tt.amountOut, tt.zeroForOne) + + } else { + actual := sqrtPriceMathGetNextSqrtPriceFromOutput(tt.sqrtPriceX96, tt.liquidity, tt.amountOut, tt.zeroForOne) + uassert.Equal(t, tt.expectedSqrtPriceX96, actual.ToString()) + } + }) + } +} + +func TestSqrtPriceMathGetAmount0DeltaHelper(t *testing.T) { + tests := []struct { + name string + sqrtRatioAX96, sqrtRatioBX96, liquidity *u256.Uint + roundUp bool + expectedAmount0Delta string + }{ + { + name: "returns 0 if liquidity is 0", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("2", "1"), + liquidity: u256.Zero(), + roundUp: true, + expectedAmount0Delta: "0", + }, + { + name: "returns 0 if prices are equal", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("1", "1"), + liquidity: u256.Zero(), + roundUp: true, + expectedAmount0Delta: "0", + }, + { + name: "returns 0.1 amount1 for price of 1 to 1.21, roundUp = true", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("121", "100"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + roundUp: true, + expectedAmount0Delta: "90909090909090910", + }, + { + name: "returns 0.1 amount1 for price of 1 to 1.21, roundUp = false", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("121", "100"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + roundUp: false, + expectedAmount0Delta: "90909090909090909", + }, + { + name: "works for prices that overflow, roundUp = true", + sqrtRatioAX96: u256.MustFromDecimal("43556142965880123323311949751266331066368"), + sqrtRatioBX96: u256.MustFromDecimal("22300745198530623141535718272648361505980416"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + roundUp: true, + expectedAmount0Delta: "1815437", + }, + { + name: "works for prices that overflow, roundUp = false", + sqrtRatioAX96: u256.MustFromDecimal("43556142965880123323311949751266331066368"), + sqrtRatioBX96: u256.MustFromDecimal("22300745198530623141535718272648361505980416"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + roundUp: false, + expectedAmount0Delta: "1815436", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := sqrtPriceMathGetAmount0DeltaHelper(tt.sqrtRatioAX96, tt.sqrtRatioBX96, tt.liquidity, tt.roundUp) + uassert.Equal(t, tt.expectedAmount0Delta, actual.ToString()) + }) + } +} + +func TestSqrtPriceMathGetAmount1DeltaHelper(t *testing.T) { + tests := []struct { + name string + sqrtRatioAX96, sqrtRatioBX96, liquidity *u256.Uint + roundUp bool + expectedAmount1Delta string + }{ + { + name: "returns 0 if liquidity is 0", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("2", "1"), + liquidity: u256.Zero(), + roundUp: true, + expectedAmount1Delta: "0", + }, + { + name: "returns 0 if prices are equal", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("1", "1"), + liquidity: u256.Zero(), + roundUp: true, + expectedAmount1Delta: "0", + }, + { + name: "returns 0.1 amount1 for price of 1 to 1.21, roundUp = true", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("121", "100"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + roundUp: true, + expectedAmount1Delta: "100000000000000000", + }, + { + name: "returns 0.1 amount1 for price of 1 to 1.21, roundUp = false", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("121", "100"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + roundUp: false, + expectedAmount1Delta: "99999999999999999", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := sqrtPriceMathGetAmount1DeltaHelper(tt.sqrtRatioAX96, tt.sqrtRatioBX96, tt.liquidity, tt.roundUp) + uassert.Equal(t, tt.expectedAmount1Delta, actual.ToString()) + }) + } +} + +func TestSwapComputation_SqrtP_SqrtQ_Mul_Overflow(t *testing.T) { + sqrtP := u256.MustFromDecimal("1025574284609383690408304870162715216695788925244") + liquidity := u256.MustFromDecimal("50015962439936049619261659728067971248") + amountIn := u256.MustFromDecimal("406") + zeroForOne := true + + sqrtQ := sqrtPriceMathGetNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne) + uassert.Equal(t, "1025574284609383582644711336373707553698163132913", sqrtQ.ToString()) + + amount0Delta := sqrtPriceMathGetAmount0DeltaHelper(sqrtQ, sqrtP, liquidity, true) + uassert.Equal(t, "406", amount0Delta.ToString()) +} + +// encodePriceSqrt calculates the sqrt((reserve1 << 192) / reserve0) +func encodePriceSqrt(reserve1, reserve0 string) *u256.Uint { + 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(ratioX192) +} + +// sqrt computes the integer square root of a u256.Uint +func sqrt(x *u256.Uint) *u256.Uint { + 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 + + for y.Cmp(z) < 0 { + z.Set(y) + temp := new(u256.Uint).Div(x, z) + y.Add(z, temp).Rsh(y, 1) + } + return z +}