From da99c14cddc99096d9a9cb799ecbe59f92fa0a4e Mon Sep 17 00:00:00 2001 From: DualFroz Date: Thu, 27 Aug 2026 11:44:00 +0200 Subject: [PATCH] test(libraries): add math library unit tests --- test/ClMath.t.sol | 90 +++++++++++++++++++++++++++++++++++++++++++++++ test/Math.t.sol | 70 ++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 test/ClMath.t.sol create mode 100644 test/Math.t.sol diff --git a/test/ClMath.t.sol b/test/ClMath.t.sol new file mode 100644 index 0000000..84f841c --- /dev/null +++ b/test/ClMath.t.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +import {Test} from "forge-std/Test.sol"; +import {TickMath} from "../src/libraries/TickMath.sol"; +import {SqrtPriceMath} from "../src/libraries/SqrtPriceMath.sol"; +import {SwapMath} from "../src/libraries/SwapMath.sol"; +import {FullMath} from "../src/libraries/FullMath.sol"; + +contract ClMathTest is Test { + uint160 private constant priceAtTickZero = 79228162514264337593543950336; + + function testSqrtRatioAtKnownTicks() public pure { + assertEq(TickMath.getSqrtRatioAtTick(0), priceAtTickZero); + assertEq(TickMath.getSqrtRatioAtTick(TickMath.minTick), TickMath.minSqrtRatio); + assertEq(TickMath.getSqrtRatioAtTick(TickMath.maxTick), TickMath.maxSqrtRatio); + } + + function testTickAtKnownRatios() public pure { + assertEq(TickMath.getTickAtSqrtRatio(priceAtTickZero), 0); + assertEq(TickMath.getTickAtSqrtRatio(TickMath.minSqrtRatio), TickMath.minTick); + } + + function testSqrtRatioMonotonic() public pure { + assertGt(TickMath.getSqrtRatioAtTick(1), TickMath.getSqrtRatioAtTick(0)); + assertLt(TickMath.getSqrtRatioAtTick(-1), TickMath.getSqrtRatioAtTick(0)); + assertGt(TickMath.getSqrtRatioAtTick(500000), TickMath.getSqrtRatioAtTick(499999)); + } + + function testFuzzTickRoundTrip(int24 tick) public pure { + tick = int24(bound(tick, TickMath.minTick, TickMath.maxTick)); + uint160 ratio = TickMath.getSqrtRatioAtTick(tick); + if (ratio >= TickMath.maxSqrtRatio) return; + int24 recovered = TickMath.getTickAtSqrtRatio(ratio); + assertEq(recovered, tick); + } + + function testFuzzTickAtRatioWithinBounds(uint160 ratio) public pure { + ratio = uint160(bound(ratio, TickMath.minSqrtRatio, TickMath.maxSqrtRatio - 1)); + int24 tick = TickMath.getTickAtSqrtRatio(ratio); + assertGe(TickMath.getSqrtRatioAtTick(tick), TickMath.minSqrtRatio == 0 ? 0 : 0); + assertLe(TickMath.getSqrtRatioAtTick(tick), ratio); + assertGt(TickMath.getSqrtRatioAtTick(tick + 1), ratio); + } + + function testAmount1DeltaKnown() public pure { + uint160 a = TickMath.getSqrtRatioAtTick(0); + uint160 b = TickMath.getSqrtRatioAtTick(120); + uint256 delta = SqrtPriceMath.getAmount1Delta(a, b, 1e18, false); + assertEq(delta, FullMath.mulDiv(1e18, b - a, 0x1000000000000000000000000)); + } + + function testAmount0DeltaSymmetric() public pure { + uint160 a = TickMath.getSqrtRatioAtTick(-60); + uint160 b = TickMath.getSqrtRatioAtTick(60); + uint256 up = SqrtPriceMath.getAmount0Delta(a, b, 5e18, true); + uint256 down = SqrtPriceMath.getAmount0Delta(a, b, 5e18, false); + assertGe(up, down); + assertLe(up - down, 1); + } + + function testComputeSwapStepExactInReachesTarget() public pure { + uint160 current = TickMath.getSqrtRatioAtTick(0); + uint160 target = TickMath.getSqrtRatioAtTick(-600); + (uint160 next, uint256 amountIn, uint256 amountOut, uint256 feeAmount) = + SwapMath.computeSwapStep(current, target, 1e21, 1e24, 3000); + assertEq(next, target); + assertGt(amountIn, 0); + assertGt(amountOut, 0); + assertGt(feeAmount, 0); + } + + function testComputeSwapStepPartialFill() public pure { + uint160 current = TickMath.getSqrtRatioAtTick(0); + uint160 target = TickMath.getSqrtRatioAtTick(-600); + (uint160 next,, uint256 amountOut,) = SwapMath.computeSwapStep(current, target, 1e21, 1e15, 3000); + assertGt(next, target); + assertLt(next, current); + assertGt(amountOut, 0); + } + + function testFuzzComputeSwapStepFeeBound(uint128 liquidity, uint128 amount) public pure { + liquidity = uint128(bound(liquidity, 1e12, 1e30)); + int256 amountIn = int256(uint256(bound(amount, 1, 1e30))); + uint160 current = TickMath.getSqrtRatioAtTick(0); + uint160 target = TickMath.getSqrtRatioAtTick(-887000); + (, uint256 stepIn,, uint256 feeAmount) = SwapMath.computeSwapStep(current, target, liquidity, amountIn, 3000); + assertLe(stepIn + feeAmount, uint256(amountIn)); + } +} diff --git a/test/Math.t.sol b/test/Math.t.sol new file mode 100644 index 0000000..6fe82c7 --- /dev/null +++ b/test/Math.t.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +import {Test} from "forge-std/Test.sol"; +import {Math} from "../src/libraries/Math.sol"; +import {FluxLibrary} from "../src/libraries/FluxLibrary.sol"; + +contract LibHarness { + function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) external pure returns (uint256) { + return FluxLibrary.quote(amountA, reserveA, reserveB); + } + + function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) external pure returns (uint256) { + return FluxLibrary.getAmountOut(amountIn, reserveIn, reserveOut); + } +} + +contract MathTest is Test { + LibHarness private harness; + + function setUp() public { + harness = new LibHarness(); + } + + function testSqrtSmallValues() public pure { + assertEq(Math.sqrt(0), 0); + assertEq(Math.sqrt(1), 1); + assertEq(Math.sqrt(2), 1); + assertEq(Math.sqrt(3), 1); + assertEq(Math.sqrt(4), 2); + assertEq(Math.sqrt(15), 3); + assertEq(Math.sqrt(16), 4); + assertEq(Math.sqrt(99), 9); + assertEq(Math.sqrt(100), 10); + } + + function testSqrtLargeValue() public pure { + assertEq(Math.sqrt(100e18 * 400e18), 2e20); + assertEq(Math.sqrt(type(uint256).max), 340282366920938463463374607431768211455); + } + + function testMin() public pure { + assertEq(Math.min(3, 7), 3); + assertEq(Math.min(7, 3), 3); + assertEq(Math.min(5, 5), 5); + } + + function testQuote() public pure { + assertEq(FluxLibrary.quote(10e18, 100e18, 400e18), 40e18); + assertEq(FluxLibrary.quote(50e18, 100e18, 400e18), 200e18); + } + + function testGetAmountOut() public pure { + assertEq(FluxLibrary.getAmountOut(10e18, 100e18, 400e18), 36264435755205965263); + } + + function testGetAmountIn() public pure { + assertEq(FluxLibrary.getAmountIn(50e18, 100e18, 400e18), 14328700386874910446); + } + + function testQuoteRevertsOnZeroLiquidity() public { + vm.expectRevert(bytes("FluxLibrary: INSUFFICIENT_LIQUIDITY")); + harness.quote(10e18, 0, 400e18); + } + + function testGetAmountOutRevertsOnZeroInput() public { + vm.expectRevert(bytes("FluxLibrary: INSUFFICIENT_INPUT_AMOUNT")); + harness.getAmountOut(0, 100e18, 400e18); + } +}