diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap index d338b386..5d0631c5 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap @@ -1 +1 @@ -354468 \ No newline at end of file +354066 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap index e8cc0afa..4c9d4bc6 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap @@ -1 +1 @@ -169905 \ No newline at end of file +169503 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap index ab1c22f2..93049819 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap @@ -1 +1 @@ -240616 \ No newline at end of file +240214 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap index 414790f5..6fc34e01 100644 --- a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap @@ -1 +1 @@ -117090 \ No newline at end of file +116696 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap index 459f60e3..8b31b571 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap @@ -1 +1 @@ -133844 \ No newline at end of file +133828 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap index ba67e132..24201514 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap @@ -1 +1 @@ -166609 \ No newline at end of file +166601 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap index 4da8d1af..30fa2821 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap @@ -1 +1 @@ -153974 \ No newline at end of file +153966 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap index e84c957a..5da556cc 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap @@ -1 +1 @@ -146019 \ No newline at end of file +146003 \ No newline at end of file diff --git a/.forge-snapshots/SqrtPriceMathTest#getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap b/.forge-snapshots/SqrtPriceMathTest#getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap index a99c3866..3456e740 100644 --- a/.forge-snapshots/SqrtPriceMathTest#getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap +++ b/.forge-snapshots/SqrtPriceMathTest#getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap @@ -1 +1 @@ -380 \ No newline at end of file +372 \ No newline at end of file diff --git a/.forge-snapshots/SqrtPriceMathTest#getNextSqrtPriceFromInput_zeroForOneEqualsTrue.snap b/.forge-snapshots/SqrtPriceMathTest#getNextSqrtPriceFromInput_zeroForOneEqualsTrue.snap index 8f7277e7..1683d110 100644 --- a/.forge-snapshots/SqrtPriceMathTest#getNextSqrtPriceFromInput_zeroForOneEqualsTrue.snap +++ b/.forge-snapshots/SqrtPriceMathTest#getNextSqrtPriceFromInput_zeroForOneEqualsTrue.snap @@ -1 +1 @@ -571 \ No newline at end of file +563 \ No newline at end of file diff --git a/.forge-snapshots/SqrtPriceMathTest#getNextSqrtPriceFromOutput_zeroForOneEqualsFalse.snap b/.forge-snapshots/SqrtPriceMathTest#getNextSqrtPriceFromOutput_zeroForOneEqualsFalse.snap index 0d20fd5f..6838ebe0 100644 --- a/.forge-snapshots/SqrtPriceMathTest#getNextSqrtPriceFromOutput_zeroForOneEqualsFalse.snap +++ b/.forge-snapshots/SqrtPriceMathTest#getNextSqrtPriceFromOutput_zeroForOneEqualsFalse.snap @@ -1 +1 @@ -582 \ No newline at end of file +574 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInCapped.snap b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInCapped.snap index 04602d5d..e6498171 100644 --- a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInCapped.snap +++ b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInCapped.snap @@ -1 +1 @@ -1296 \ No newline at end of file +1288 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutCapped.snap b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutCapped.snap index a624bd7e..304d0b05 100644 --- a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutCapped.snap +++ b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutCapped.snap @@ -1 +1 @@ -1133 \ No newline at end of file +1125 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInCapped.snap b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInCapped.snap index 568efcad..3b2a35fa 100644 --- a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInCapped.snap +++ b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInCapped.snap @@ -1 +1 @@ -1301 \ No newline at end of file +1293 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInPartial.snap b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInPartial.snap index ce70acdc..cab92437 100644 --- a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInPartial.snap +++ b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInPartial.snap @@ -1 +1 @@ -2118 \ No newline at end of file +2110 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutCapped.snap b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutCapped.snap index 0b078c5a..4ee16cc3 100644 --- a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutCapped.snap +++ b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutCapped.snap @@ -1 +1 @@ -1130 \ No newline at end of file +1122 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutPartial.snap b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutPartial.snap index ce70acdc..cab92437 100644 --- a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutPartial.snap +++ b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutPartial.snap @@ -1 +1 @@ -2118 \ No newline at end of file +2110 \ No newline at end of file diff --git a/.forge-snapshots/TickTest#tickSpacingToMaxLiquidityPerTick.snap b/.forge-snapshots/TickTest#tickSpacingToMaxLiquidityPerTick.snap index 1683d110..d99e90eb 100644 --- a/.forge-snapshots/TickTest#tickSpacingToMaxLiquidityPerTick.snap +++ b/.forge-snapshots/TickTest#tickSpacingToMaxLiquidityPerTick.snap @@ -1 +1 @@ -563 \ No newline at end of file +29 \ No newline at end of file diff --git a/.forge-snapshots/TickTest#update.snap b/.forge-snapshots/TickTest#update.snap index 2b39a06d..cdb4e3f8 100644 --- a/.forge-snapshots/TickTest#update.snap +++ b/.forge-snapshots/TickTest#update.snap @@ -1 +1 @@ -133918 \ No newline at end of file +133389 \ No newline at end of file diff --git a/src/pool-cl/libraries/FullMath.sol b/src/pool-cl/libraries/FullMath.sol index 930c94c2..0abbd42d 100644 --- a/src/pool-cl/libraries/FullMath.sol +++ b/src/pool-cl/libraries/FullMath.sol @@ -110,9 +110,8 @@ library FullMath { function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { unchecked { result = mulDiv(a, b, denominator); - if (mulmod(a, b, denominator) > 0) { - require(result < type(uint256).max); - result++; + if (mulmod(a, b, denominator) != 0) { + require(++result > 0); } } } diff --git a/src/pool-cl/libraries/Tick.sol b/src/pool-cl/libraries/Tick.sol index 5dd8060a..4c3f51c7 100644 --- a/src/pool-cl/libraries/Tick.sol +++ b/src/pool-cl/libraries/Tick.sol @@ -50,12 +50,22 @@ library Tick { /// @dev Executed within the pool constructor /// @param tickSpacing The amount of required tick separation, realized in multiples of `tickSpacing` /// e.g., a tickSpacing of 3 requires ticks to be initialized every 3rd tick i.e., ..., -6, -3, 0, 3, 6, ... - /// @return The max liquidity per tick - function tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) internal pure returns (uint128) { - int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing; - int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing; - uint24 numTicks = uint24((maxTick - minTick) / tickSpacing) + 1; - return type(uint128).max / numTicks; + /// @return result The max liquidity per tick + function tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) internal pure returns (uint128 result) { + // Equivalent to v3 but in assembly for gas efficiency: + // int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing; + // int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing; + // uint24 numTicks = uint24((maxTick - minTick) / tickSpacing) + 1; + // return type(uint128).max / numTicks; + int24 MAX_TICK = TickMath.MAX_TICK; + int24 MIN_TICK = TickMath.MIN_TICK; + // tick spacing will never be 0 since TickMath.MIN_TICK_SPACING is 1 + assembly { + let minTick := mul(sdiv(MIN_TICK, tickSpacing), tickSpacing) + let maxTick := mul(sdiv(MAX_TICK, tickSpacing), tickSpacing) + let numTicks := add(sdiv(sub(maxTick, minTick), tickSpacing), 1) + result := div(sub(shl(128, 1), 1), numTicks) + } } /// @notice Retrieves fee growth data diff --git a/test/pool-cl/helpers/PoolModifyPositionTest.sol b/test/pool-cl/helpers/PoolModifyPositionTest.sol deleted file mode 100644 index 8f19d4a5..00000000 --- a/test/pool-cl/helpers/PoolModifyPositionTest.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; - -import {console2} from "forge-std/console2.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {CurrencyLibrary, Currency} from "../../../src/types/Currency.sol"; -import {CurrencySettlement} from "../../helpers/CurrencySettlement.sol"; -import {ILockCallback} from "../../../src/interfaces//ILockCallback.sol"; -import {ICLPoolManager} from "../../../src/pool-cl/interfaces/ICLPoolManager.sol"; -import {IVault} from "../../../src/interfaces/IVault.sol"; - -import {BalanceDelta} from "../../../src/types/BalanceDelta.sol"; -import {PoolKey} from "../../../src/types/PoolKey.sol"; - -contract PoolModifyPositionTest is ILockCallback { - using CurrencySettlement for Currency; - - IVault public immutable vault; - ICLPoolManager public immutable manager; - - constructor(IVault _vault, ICLPoolManager _manager) { - vault = _vault; - manager = _manager; - } - - struct CallbackData { - address sender; - PoolKey key; - ICLPoolManager.ModifyLiquidityParams params; - bytes hookData; - } - - function modifyPosition( - PoolKey memory key, - ICLPoolManager.ModifyLiquidityParams memory params, - bytes memory hookData - ) external payable returns (BalanceDelta delta) { - delta = abi.decode(vault.lock(abi.encode(CallbackData(msg.sender, key, params, hookData))), (BalanceDelta)); - - uint256 ethBalance = address(this).balance; - if (ethBalance > 0) { - CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); - } - } - - function lockAcquired(bytes calldata rawData) external returns (bytes memory) { - require(msg.sender == address(vault)); - - CallbackData memory data = abi.decode(rawData, (CallbackData)); - - (BalanceDelta delta, BalanceDelta feeDelta) = manager.modifyLiquidity(data.key, data.params, data.hookData); - - // For now assume to always settle feeDelta in the same way as delta - BalanceDelta totalDelta = delta + feeDelta; - - if (totalDelta.amount0() > 0) { - data.key.currency0.settle(vault, data.sender, uint128(totalDelta.amount0()), false); - } - if (totalDelta.amount1() > 0) { - data.key.currency1.settle(vault, data.sender, uint128(totalDelta.amount1()), false); - } - - if (totalDelta.amount0() < 0) { - data.key.currency0.take(vault, data.sender, uint128(-totalDelta.amount0()), false); - } - if (totalDelta.amount1() < 0) { - data.key.currency1.take(vault, data.sender, uint128(-totalDelta.amount0()), false); - } - - return abi.encode(totalDelta); - } -} diff --git a/test/pool-cl/libraries/FullMath.t.sol b/test/pool-cl/libraries/FullMath.t.sol index 37e6a441..eada6bae 100644 --- a/test/pool-cl/libraries/FullMath.t.sol +++ b/test/pool-cl/libraries/FullMath.t.sol @@ -52,7 +52,7 @@ contract FullMathTest is Test { function test_mulDiv_fuzz(uint256 x, uint256 y, uint256 d) public { vm.assume(d != 0); vm.assume(y != 0); - vm.assume(x <= type(uint256).max / y); + x = bound(x, 0, type(uint256).max / y); assertEq(FullMath.mulDiv(x, y, d), x * y / d); } @@ -97,10 +97,14 @@ contract FullMathTest is Test { function test_mulDivRoundingUp_fuzz(uint256 x, uint256 y, uint256 d) public { vm.assume(d != 0); vm.assume(y != 0); - vm.assume(x <= type(uint256).max / y); + x = bound(x, 0, type(uint256).max / y); uint256 numerator = x * y; uint256 result = FullMath.mulDivRoundingUp(x, y, d); - assertTrue(result == numerator / d || result == numerator / d + 1); + if (mulmod(x, y, d) > 0) { + assertEq(result, numerator / d + 1); + } else { + assertEq(result, numerator / d); + } } function test_mulDivRounding(uint256 x, uint256 y, uint256 d) public { diff --git a/test/pool-cl/libraries/Tick.t.sol b/test/pool-cl/libraries/Tick.t.sol index f739c016..1a7c4eaf 100644 --- a/test/pool-cl/libraries/Tick.t.sol +++ b/test/pool-cl/libraries/Tick.t.sol @@ -542,4 +542,14 @@ contract TickTest is Test, GasSnapshot { // max liquidity at every tick is less than the cap assertGe(type(uint128).max, uint256(maxLiquidityPerTick) * numTicks); } + + function test_fuzz_tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) public pure { + vm.assume(tickSpacing > 0); + // v3 math + int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing; + int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing; + uint24 numTicks = uint24((maxTick - minTick) / tickSpacing) + 1; + // assert that the result is the same as the v3 math + assertEq(type(uint128).max / numTicks, Tick.tickSpacingToMaxLiquidityPerTick(tickSpacing)); + } }