diff --git a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap index 318a3319..5f00a595 100644 --- a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap @@ -1 +1 @@ -180732 \ No newline at end of file +180720 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap index bcf21868..55e014b7 100644 --- a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap @@ -1 +1 @@ -137531 \ No newline at end of file +137534 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap index 0a4a95a6..b250e5b8 100644 --- a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap @@ -1 +1 @@ -331216 \ No newline at end of file +331204 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testExtLoadPoolActiveId.snap b/.forge-snapshots/BinPoolManagerTest#testExtLoadPoolActiveId.snap index f9d12350..34f7d67c 100644 --- a/.forge-snapshots/BinPoolManagerTest#testExtLoadPoolActiveId.snap +++ b/.forge-snapshots/BinPoolManagerTest#testExtLoadPoolActiveId.snap @@ -1 +1 @@ -1824 \ No newline at end of file +1818 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap b/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap index 8e96cfd1..960d5d6f 100644 --- a/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap +++ b/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap @@ -1 +1 @@ -30405 \ No newline at end of file +30417 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap b/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap index 7830e9bd..1de7d744 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap @@ -1 +1 @@ -119509 \ No newline at end of file +119497 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap b/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap index 2397c98f..4fc7e052 100644 --- a/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap +++ b/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap @@ -1 +1 @@ -34487 \ No newline at end of file +34340 \ No newline at end of file diff --git a/.forge-snapshots/BitMathTest#leastSignificantBitMaxUint128.snap b/.forge-snapshots/BitMathTest#leastSignificantBitMaxUint128.snap index 9d3f4170..ee2b8364 100644 --- a/.forge-snapshots/BitMathTest#leastSignificantBitMaxUint128.snap +++ b/.forge-snapshots/BitMathTest#leastSignificantBitMaxUint128.snap @@ -1 +1 @@ -455 \ No newline at end of file +456 \ No newline at end of file diff --git a/.forge-snapshots/BitMathTest#leastSignificantBitMaxUint256.snap b/.forge-snapshots/BitMathTest#leastSignificantBitMaxUint256.snap index 2da43253..7d4983b9 100644 --- a/.forge-snapshots/BitMathTest#leastSignificantBitMaxUint256.snap +++ b/.forge-snapshots/BitMathTest#leastSignificantBitMaxUint256.snap @@ -1 +1 @@ -457 \ No newline at end of file +458 \ No newline at end of file diff --git a/.forge-snapshots/BitMathTest#leastSignificantBitSmallNumber.snap b/.forge-snapshots/BitMathTest#leastSignificantBitSmallNumber.snap index 13ef0a79..a21cae36 100644 --- a/.forge-snapshots/BitMathTest#leastSignificantBitSmallNumber.snap +++ b/.forge-snapshots/BitMathTest#leastSignificantBitSmallNumber.snap @@ -1 +1 @@ -453 \ No newline at end of file +454 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap index 9c70c736..f78bb357 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap @@ -1 +1 @@ -360023 \ No newline at end of file +358846 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap index d20d8886..75d41017 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap @@ -1 +1 @@ -175191 \ No newline at end of file +174344 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap index 202906bc..254e9e2d 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap @@ -1 +1 @@ -245111 \ No newline at end of file +244098 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap index 9dc3012f..df07f99c 100644 --- a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap @@ -1 +1 @@ -112417 \ No newline at end of file +112420 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap index e5314134..60d0220b 100644 --- a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap @@ -1 +1 @@ -59963 \ No newline at end of file +59775 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap index 6c3a23f4..9463b024 100644 --- a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap @@ -1 +1 @@ -121271 \ No newline at end of file +120646 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap index aff23368..20e38caa 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap @@ -1 +1 @@ -137753 \ No newline at end of file +137041 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap index 8c915d81..39d49ec8 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap @@ -1 +1 @@ -170905 \ No newline at end of file +169737 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap index f79b46ec..0429262d 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap @@ -1 +1 @@ -157667 \ No newline at end of file +156973 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap index 88bee2d5..e394cf92 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap @@ -1 +1 @@ -76212 \ No newline at end of file +75510 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap index a10c22f9..14d68445 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap @@ -1 +1 @@ -149916 \ No newline at end of file +149059 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap index 8d1e981c..96284da2 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap @@ -1 +1 @@ -93104 \ No newline at end of file +92402 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap index b8008c9c..f8404ef8 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap @@ -1 +1 @@ -76215 \ No newline at end of file +75513 \ No newline at end of file diff --git a/.forge-snapshots/SqrtPriceMathTest#getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap b/.forge-snapshots/SqrtPriceMathTest#getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap index e157b5b5..8c19d14d 100644 --- a/.forge-snapshots/SqrtPriceMathTest#getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap +++ b/.forge-snapshots/SqrtPriceMathTest#getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse.snap @@ -1 +1 @@ -520 \ No newline at end of file +369 \ No newline at end of file diff --git a/.forge-snapshots/SqrtPriceMathTest#getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap b/.forge-snapshots/SqrtPriceMathTest#getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap index 70e36030..6c8526a8 100644 --- a/.forge-snapshots/SqrtPriceMathTest#getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap +++ b/.forge-snapshots/SqrtPriceMathTest#getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue.snap @@ -1 +1 @@ -648 \ No newline at end of file +370 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInCapped.snap b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInCapped.snap index a3bcf894..59730012 100644 --- a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInCapped.snap +++ b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInCapped.snap @@ -1 +1 @@ -1942 \ No newline at end of file +1673 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInPartial.snap b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInPartial.snap index 8e0971a4..8615bfd5 100644 --- a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInPartial.snap +++ b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInPartial.snap @@ -1 +1 @@ -2697 \ No newline at end of file +2147 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutCapped.snap b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutCapped.snap index 31edc2fc..5bb1b749 100644 --- a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutCapped.snap +++ b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutCapped.snap @@ -1 +1 @@ -1692 \ No newline at end of file +1423 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutPartial.snap b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutPartial.snap index 8e0971a4..8615bfd5 100644 --- a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutPartial.snap +++ b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutPartial.snap @@ -1 +1 @@ -2697 \ No newline at end of file +2147 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInCapped.snap b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInCapped.snap index 085a6265..63d53b76 100644 --- a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInCapped.snap +++ b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInCapped.snap @@ -1 +1 @@ -1911 \ No newline at end of file +1769 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInPartial.snap b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInPartial.snap index 2a9d55ff..97fe63cf 100644 --- a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInPartial.snap +++ b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInPartial.snap @@ -1 +1 @@ -2831 \ No newline at end of file +2689 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutCapped.snap b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutCapped.snap index 1a0e9267..929e30ed 100644 --- a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutCapped.snap +++ b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutCapped.snap @@ -1 +1 @@ -1661 \ No newline at end of file +1519 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutPartial.snap b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutPartial.snap index 2a9d55ff..97fe63cf 100644 --- a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutPartial.snap +++ b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutPartial.snap @@ -1 +1 @@ -2831 \ No newline at end of file +2689 \ No newline at end of file diff --git a/.forge-snapshots/TickTest#checkTicks.snap b/.forge-snapshots/TickTest#checkTicks.snap index 0dbf139f..cb37cb5c 100644 --- a/.forge-snapshots/TickTest#checkTicks.snap +++ b/.forge-snapshots/TickTest#checkTicks.snap @@ -1 +1 @@ -350 \ No newline at end of file +186 \ No newline at end of file diff --git a/.forge-snapshots/TickTest#tickSpacingToMaxLiquidityPerTick.snap b/.forge-snapshots/TickTest#tickSpacingToMaxLiquidityPerTick.snap index 5ec4258d..4aae4fb6 100644 --- a/.forge-snapshots/TickTest#tickSpacingToMaxLiquidityPerTick.snap +++ b/.forge-snapshots/TickTest#tickSpacingToMaxLiquidityPerTick.snap @@ -1 +1 @@ -1159 \ No newline at end of file +1072 \ No newline at end of file diff --git a/.forge-snapshots/TickTest#update.snap b/.forge-snapshots/TickTest#update.snap index 201f1298..1f43cf81 100644 --- a/.forge-snapshots/TickTest#update.snap +++ b/.forge-snapshots/TickTest#update.snap @@ -1 +1 @@ -134827 \ No newline at end of file +134740 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#collectFee.snap b/.forge-snapshots/VaultTest#collectFee.snap index f1743106..8164d7ba 100644 --- a/.forge-snapshots/VaultTest#collectFee.snap +++ b/.forge-snapshots/VaultTest#collectFee.snap @@ -1 +1 @@ -25138 \ No newline at end of file +25135 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap b/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap index a546fb0b..6ab7773f 100644 --- a/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap +++ b/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap @@ -1 +1 @@ -81463 \ No newline at end of file +81494 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#lockSettledWhenFlashloan.snap b/.forge-snapshots/VaultTest#lockSettledWhenFlashloan.snap index 397eead4..19aba2d5 100644 --- a/.forge-snapshots/VaultTest#lockSettledWhenFlashloan.snap +++ b/.forge-snapshots/VaultTest#lockSettledWhenFlashloan.snap @@ -1 +1 @@ -121269 \ No newline at end of file +121363 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap b/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap index 2e26a0a0..25fd05b9 100644 --- a/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap +++ b/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap @@ -1 +1 @@ -45005 \ No newline at end of file +45036 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap b/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap index b8e90862..9db8a723 100644 --- a/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap +++ b/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap @@ -1 +1 @@ -45004 \ No newline at end of file +45032 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#registerPoolManager.snap b/.forge-snapshots/VaultTest#registerPoolManager.snap index d3457db4..223e86f2 100644 --- a/.forge-snapshots/VaultTest#registerPoolManager.snap +++ b/.forge-snapshots/VaultTest#registerPoolManager.snap @@ -1 +1 @@ -47916 \ No newline at end of file +47913 \ No newline at end of file diff --git a/.forge-snapshots/flipTick_gasCostOfFlippingATickThatResultsInDeletingAWord.snap b/.forge-snapshots/flipTick_gasCostOfFlippingATickThatResultsInDeletingAWord.snap index 3819e938..a411878b 100644 --- a/.forge-snapshots/flipTick_gasCostOfFlippingATickThatResultsInDeletingAWord.snap +++ b/.forge-snapshots/flipTick_gasCostOfFlippingATickThatResultsInDeletingAWord.snap @@ -1 +1 @@ -5409 \ No newline at end of file +5244 \ No newline at end of file diff --git a/.forge-snapshots/flipTick_gasCostOfFlippingFirstTickInWordToInitialized.snap b/.forge-snapshots/flipTick_gasCostOfFlippingFirstTickInWordToInitialized.snap index 0cd31fe2..bbfdf6e0 100644 --- a/.forge-snapshots/flipTick_gasCostOfFlippingFirstTickInWordToInitialized.snap +++ b/.forge-snapshots/flipTick_gasCostOfFlippingFirstTickInWordToInitialized.snap @@ -1 +1 @@ -22506 \ No newline at end of file +22341 \ No newline at end of file diff --git a/.forge-snapshots/flipTick_gasCostOfFlippingSecondTickInWordToInitialized.snap b/.forge-snapshots/flipTick_gasCostOfFlippingSecondTickInWordToInitialized.snap index 38966a2c..e9d178a3 100644 --- a/.forge-snapshots/flipTick_gasCostOfFlippingSecondTickInWordToInitialized.snap +++ b/.forge-snapshots/flipTick_gasCostOfFlippingSecondTickInWordToInitialized.snap @@ -1 +1 @@ -5515 \ No newline at end of file +5350 \ No newline at end of file diff --git a/.forge-snapshots/nextInitializedTickWithinOneWord_lteFalse_gasCostForEntireWord.snap b/.forge-snapshots/nextInitializedTickWithinOneWord_lteFalse_gasCostForEntireWord.snap index 13f668d1..0203089e 100644 --- a/.forge-snapshots/nextInitializedTickWithinOneWord_lteFalse_gasCostForEntireWord.snap +++ b/.forge-snapshots/nextInitializedTickWithinOneWord_lteFalse_gasCostForEntireWord.snap @@ -1 +1 @@ -2592 \ No newline at end of file +2488 \ No newline at end of file diff --git a/.forge-snapshots/nextInitializedTickWithinOneWord_lteFalse_gasCostJustBelowBoundary.snap b/.forge-snapshots/nextInitializedTickWithinOneWord_lteFalse_gasCostJustBelowBoundary.snap index 13f668d1..0203089e 100644 --- a/.forge-snapshots/nextInitializedTickWithinOneWord_lteFalse_gasCostJustBelowBoundary.snap +++ b/.forge-snapshots/nextInitializedTickWithinOneWord_lteFalse_gasCostJustBelowBoundary.snap @@ -1 +1 @@ -2592 \ No newline at end of file +2488 \ No newline at end of file diff --git a/.forge-snapshots/nextInitializedTickWithinOneWord_lteFalse_gasCostOnBoundary.snap b/.forge-snapshots/nextInitializedTickWithinOneWord_lteFalse_gasCostOnBoundary.snap index 13f668d1..0203089e 100644 --- a/.forge-snapshots/nextInitializedTickWithinOneWord_lteFalse_gasCostOnBoundary.snap +++ b/.forge-snapshots/nextInitializedTickWithinOneWord_lteFalse_gasCostOnBoundary.snap @@ -1 +1 @@ -2592 \ No newline at end of file +2488 \ No newline at end of file diff --git a/.forge-snapshots/nextInitializedTickWithinOneWord_lteTrue_gasCostForEntireWord.snap b/.forge-snapshots/nextInitializedTickWithinOneWord_lteTrue_gasCostForEntireWord.snap index 0c52fc6d..de23d9ec 100644 --- a/.forge-snapshots/nextInitializedTickWithinOneWord_lteTrue_gasCostForEntireWord.snap +++ b/.forge-snapshots/nextInitializedTickWithinOneWord_lteTrue_gasCostForEntireWord.snap @@ -1 +1 @@ -2591 \ No newline at end of file +2475 \ No newline at end of file diff --git a/.forge-snapshots/nextInitializedTickWithinOneWord_lteTrue_gasCostJustBelowBoundary.snap b/.forge-snapshots/nextInitializedTickWithinOneWord_lteTrue_gasCostJustBelowBoundary.snap index 7b34ebbf..a4e1baa5 100644 --- a/.forge-snapshots/nextInitializedTickWithinOneWord_lteTrue_gasCostJustBelowBoundary.snap +++ b/.forge-snapshots/nextInitializedTickWithinOneWord_lteTrue_gasCostJustBelowBoundary.snap @@ -1 +1 @@ -2900 \ No newline at end of file +2784 \ No newline at end of file diff --git a/.forge-snapshots/nextInitializedTickWithinOneWord_lteTrue_gasCostOnBoundary.snap b/.forge-snapshots/nextInitializedTickWithinOneWord_lteTrue_gasCostOnBoundary.snap index 0c52fc6d..de23d9ec 100644 --- a/.forge-snapshots/nextInitializedTickWithinOneWord_lteTrue_gasCostOnBoundary.snap +++ b/.forge-snapshots/nextInitializedTickWithinOneWord_lteTrue_gasCostOnBoundary.snap @@ -1 +1 @@ -2591 \ No newline at end of file +2475 \ No newline at end of file diff --git a/src/libraries/ProtocolFeeLibrary.sol b/src/libraries/ProtocolFeeLibrary.sol index 9597a36b..27ff06c5 100644 --- a/src/libraries/ProtocolFeeLibrary.sol +++ b/src/libraries/ProtocolFeeLibrary.sol @@ -8,36 +8,38 @@ library ProtocolFeeLibrary { // Max protocol fee is 0.1% (1000 pips) uint16 public constant MAX_PROTOCOL_FEE = 1000; + // Thresholds used for optimized bounds checks on protocol fees + uint24 internal constant FEE_0_THRESHOLD = 1001; + uint24 internal constant FEE_1_THRESHOLD = 1001 << 12; + // the protocol fee is represented in hundredths of a bip uint256 internal constant PIPS_DENOMINATOR = 1_000_000; function getZeroForOneFee(uint24 self) internal pure returns (uint16) { - return uint16(self & (4096 - 1)); + return uint16(self & 0xfff); } function getOneForZeroFee(uint24 self) internal pure returns (uint16) { return uint16(self >> 12); } - function validate(uint24 self) internal pure returns (bool) { - if (self != 0) { - uint16 fee0 = getZeroForOneFee(self); - uint16 fee1 = getOneForZeroFee(self); - // The fee is represented in pips and it cannot be greater than the MAX_PROTOCOL_FEE. - if ((fee0 > MAX_PROTOCOL_FEE) || (fee1 > MAX_PROTOCOL_FEE)) { - return false; - } + function validate(uint24 self) internal pure returns (bool valid) { + // Equivalent to: getZeroForOneFee(self) <= MAX_PROTOCOL_FEE && getOneForZeroFee(self) <= MAX_PROTOCOL_FEE + assembly { + let isZeroForOneFeeOk := lt(and(self, 0xfff), FEE_0_THRESHOLD) + let isOneForZeroFeeOk := lt(self, FEE_1_THRESHOLD) + valid := and(isZeroForOneFeeOk, isOneForZeroFeeOk) } - return true; } // The protocol fee is taken from the input amount first and then the LP fee is taken from the remaining // The swap fee is capped at 100% // equivalent to protocolFee + lpFee(1_000_000 - protocolFee) / 1_000_000 - function calculateSwapFee(uint24 self, uint24 lpFee) internal pure returns (uint24) { - unchecked { - uint256 numerator = uint256(self) * uint256(lpFee); - return uint24(uint256(self) + lpFee - UnsafeMath.divRoundingUp(numerator, PIPS_DENOMINATOR)); + function calculateSwapFee(uint24 self, uint24 lpFee) internal pure returns (uint24 swapFee) { + assembly { + let numerator := mul(self, lpFee) + let divRoundingUp := add(div(numerator, PIPS_DENOMINATOR), gt(mod(numerator, PIPS_DENOMINATOR), 0)) + swapFee := sub(add(self, lpFee), divRoundingUp) } } } diff --git a/src/pool-cl/libraries/SqrtPriceMath.sol b/src/pool-cl/libraries/SqrtPriceMath.sol index 7d98bcb4..8fd51568 100644 --- a/src/pool-cl/libraries/SqrtPriceMath.sol +++ b/src/pool-cl/libraries/SqrtPriceMath.sol @@ -200,6 +200,19 @@ library SqrtPriceMath { } } + /// @notice Equivalent to: `a >= b ? a - b : b - a` + function absDiff(uint160 a, uint160 b) internal pure returns (uint256 res) { + assembly { + let diff := sub(a, b) + // mask = 0 if a >= b else -1 (all 1s) + let mask := sar(255, diff) + // if a >= b, res = a - b = 0 ^ (a - b) + // if a < b, res = b - a = ~~(b - a) = ~(-(b - a) - 1) = ~(a - b - 1) = (-1) ^ (a - b - 1) + // either way, res = mask ^ (a - b + mask) + res := xor(mask, add(mask, diff)) + } + } + /// @notice Gets the amount1 delta between two prices /// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower)) /// @param sqrtRatioAX96 A sqrt price @@ -212,11 +225,24 @@ library SqrtPriceMath { pure returns (uint256 amount1) { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); - - return roundUp - ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96) - : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); + uint256 numerator = absDiff(sqrtRatioAX96, sqrtRatioBX96); + uint256 denominator = FixedPoint96.Q96; + uint256 _liquidity; + assembly { + // avoid implicit upcasting + _liquidity := liquidity + } + /** + * Equivalent to: + * amount1 = roundUp + * ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96) + * : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); + * Cannot overflow because `type(uint128).max * type(uint160).max >> 96 < (1 << 192)`. + */ + amount1 = FullMath.mulDiv(_liquidity, numerator, denominator); + assembly { + amount1 := add(amount1, and(gt(mulmod(_liquidity, numerator, denominator), 0), roundUp)) + } } /// @notice Helper that gets signed currency0 delta diff --git a/src/pool-cl/libraries/TickBitmap.sol b/src/pool-cl/libraries/TickBitmap.sol index 1d066104..f97cc614 100644 --- a/src/pool-cl/libraries/TickBitmap.sol +++ b/src/pool-cl/libraries/TickBitmap.sol @@ -13,14 +13,30 @@ library TickBitmap { /// @param tickSpacing The tick spacing of the pool error TickMisaligned(int24 tick, int24 tickSpacing); + /// @dev round towards negative infinity + function compress(int24 tick, int24 tickSpacing) internal pure returns (int24 compressed) { + // Equivalent to: + // compressed = tick / tickSpacing; + // if (tick < 0 && tick % tickSpacing != 0) compressed--; + assembly { + compressed := + sub( + sdiv(tick, tickSpacing), + // if (tick < 0 && tick % tickSpacing != 0) then tick % tickSpacing < 0, vice versa + slt(smod(tick, tickSpacing), 0) + ) + } + } + /// @notice Computes the position in the mapping where the initialized bit for a tick lives /// @param tick The tick for which to compute the position /// @return wordPos The key in the mapping containing the word in which the bit is stored /// @return bitPos The bit position in the word where the flag is stored - function position(int24 tick) private pure returns (int16 wordPos, uint8 bitPos) { - unchecked { - wordPos = int16(tick >> 8); - bitPos = uint8(int8(tick % 256)); + function position(int24 tick) internal pure returns (int16 wordPos, uint8 bitPos) { + assembly { + // signed arithmetic shift right + wordPos := sar(8, tick) // + bitPos := and(tick, 0xff) } } @@ -29,11 +45,29 @@ library TickBitmap { /// @param tick The tick to flip /// @param tickSpacing The spacing between usable ticks function flipTick(mapping(int16 => uint256) storage self, int24 tick, int24 tickSpacing) internal { - unchecked { - if (tick % tickSpacing != 0) revert TickMisaligned(tick, tickSpacing); // ensure that the tick is spaced - (int16 wordPos, uint8 bitPos) = position(tick / tickSpacing); - uint256 mask = 1 << bitPos; - self[wordPos] ^= mask; + // Equivalent to: + // if (tick % tickSpacing != 0) revert TickMisaligned(tick, tickSpacing); // ensure that the tick is spaced + // (int16 wordPos, uint8 bitPos) = position(tick / tickSpacing); + // uint256 mask = 1 << bitPos; + // self[wordPos] ^= mask; + assembly ("memory-safe") { + // ensure that the tick is spaced + if smod(tick, tickSpacing) { + mstore(0, 0xd4d8f3e6) // selector for TickMisaligned(int24,int24) + mstore(0x20, tick) + mstore(0x40, tickSpacing) + revert(0x1c, 0x44) + } + tick := sdiv(tick, tickSpacing) + // calculate the storage slot corresponding to the tick + // wordPos = tick >> 8 + mstore(0, sar(8, tick)) + mstore(0x20, self.slot) + // the slot of self[wordPos] is keccak256(abi.encode(wordPos, self.slot)) + let slot := keccak256(0, 0x40) + // mask = 1 << bitPos = 1 << (tick % 256) + // self[wordPos] ^= mask + sstore(slot, xor(sload(slot), shl(and(tick, 0xff), 1))) } } @@ -52,8 +86,7 @@ library TickBitmap { bool lte ) internal view returns (int24 next, bool initialized) { unchecked { - int24 compressed = tick / tickSpacing; - if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity + int24 compressed = compress(tick, tickSpacing); if (lte) { (int16 wordPos, uint8 bitPos) = position(compressed); @@ -69,7 +102,7 @@ library TickBitmap { : (compressed - int24(uint24(bitPos))) * tickSpacing; } else { // start from the word of the next tick, since the current tick state doesn't matter - (int16 wordPos, uint8 bitPos) = position(compressed + 1); + (int16 wordPos, uint8 bitPos) = position(++compressed); // all the 1s at or to the left of the bitPos uint256 mask = ~((1 << bitPos) - 1); uint256 masked = self[wordPos] & mask; @@ -78,8 +111,8 @@ library TickBitmap { initialized = masked != 0; // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick next = initialized - ? (compressed + 1 + int24(uint24(BitMath.leastSignificantBit(masked) - bitPos))) * tickSpacing - : (compressed + 1 + int24(uint24(type(uint8).max - bitPos))) * tickSpacing; + ? (compressed + int24(uint24(BitMath.leastSignificantBit(masked) - bitPos))) * tickSpacing + : (compressed + int24(uint24(type(uint8).max - bitPos))) * tickSpacing; } } } diff --git a/src/pool-cl/libraries/TickMath.sol b/src/pool-cl/libraries/TickMath.sol index 53557eab..7bdc86f3 100644 --- a/src/pool-cl/libraries/TickMath.sol +++ b/src/pool-cl/libraries/TickMath.sol @@ -14,12 +14,15 @@ library TickMath { /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128 int24 internal constant MIN_TICK = -887272; /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128 - int24 internal constant MAX_TICK = -MIN_TICK; + int24 internal constant MAX_TICK = 887272; /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) uint160 internal constant MIN_SQRT_RATIO = 4295128739; /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; + /// @dev A threshold used for optimized bounds check, equals `MAX_SQRT_RATIO - MIN_SQRT_RATIO - 1` + uint160 internal constant MAX_SQRT_RATIO_MINUS_MIN_SQRT_RATIO_MINUS_ONE = + 1461446703485210103287273052203988822378723970342 - 4295128739 - 1; /// @notice Given a tickSpacing, compute the maximum usable tick function maxUsableTick(int24 tickSpacing) internal pure returns (int24) { @@ -42,11 +45,30 @@ library TickMath { /// at the given tick function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { unchecked { - uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); - if (absTick > uint256(int256(MAX_TICK))) revert InvalidTick(); + // Equivalent: uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); + uint256 absTick; + assembly { + // mask = 0 if tick >= 0 else -1 (all 1s) + let mask := sar(255, tick) + absTick := xor(mask, add(mask, tick)) + } - uint256 ratio = - absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000; + // Equivalent: if (absTick > MAX_TICK) revert InvalidTick(); + assembly ("memory-safe") { + if gt(absTick, MAX_TICK) { + // store 4-byte selector of "InvalidTick()" at memory [0x1c, 0x20) + mstore(0, 0xce8ef7fc) + revert(0x1c, 0x04) + } + } + + // Equivalent to: + // ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000; + // or price = int(2**128 / sqrt(1.0001)) if (absTick & 0x1) else 1 << 128 + uint256 ratio; + assembly { + ratio := xor(shl(128, 1), mul(xor(shl(128, 1), 0xfffcb933bd6fad37aa2d162d1a594001), and(absTick, 0x1))) + } if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128; if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; @@ -67,12 +89,17 @@ library TickMath { if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128; if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128; - if (tick > 0) ratio = type(uint256).max / ratio; + assembly { + // Equivalent: if (tick > 0) ratio = type(uint256).max / ratio; + if sgt(tick, 0) { ratio := div(not(0), ratio) } - // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. - // we then downcast because we know the result always fits within 160 bits due to our tick input constraint - // we round up in the division so getTickAtSqrtRatio of the output price is always consistent - sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1)); + // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. + // we then downcast because we know the result always fits within 160 bits due to our tick input constraint + // we round up in the division so getTickAtSqrtPrice of the output price is always consistent + // `sub(shl(32, 1), 1)` is `type(uint32).max` + // `ratio + type(uint32).max` will not overflow because `ratio` fits in 192 bits + sqrtPriceX96 := shr(32, add(ratio, sub(shl(32, 1), 1))) + } } } @@ -83,8 +110,18 @@ library TickMath { /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) { unchecked { + // Equivalent: if (sqrtPriceX96 < MIN_SQRT_RATIO || sqrtPriceX96 >= MAX_SQRT_RATIO) revert InvalidSqrtRatio(); // second inequality must be < because the price can never reach the price at the max tick - if (sqrtPriceX96 < MIN_SQRT_RATIO || sqrtPriceX96 >= MAX_SQRT_RATIO) revert InvalidSqrtRatio(); + assembly ("memory-safe") { + // if sqrtPriceX96 < MIN_SQRT_PRICE, the `sub` underflows and `gt` is true + // if sqrtPriceX96 >= MAX_SQRT_PRICE, sqrtPriceX96 - MIN_SQRT_PRICE > MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1 + if gt(sub(sqrtPriceX96, MIN_SQRT_RATIO), MAX_SQRT_RATIO_MINUS_MIN_SQRT_RATIO_MINUS_ONE) { + // store 4-byte selector of "InvalidSqrtRatio()" at memory [0x1c, 0x20) + mstore(0, 0x02ad01b6) + revert(0x1c, 0x04) + } + } + uint256 ratio = uint256(sqrtPriceX96) << 32; uint256 r = ratio; diff --git a/test/libraries/ProtocolFeeLibrary.t.sol b/test/libraries/ProtocolFeeLibrary.t.sol new file mode 100644 index 00000000..92dc7c07 --- /dev/null +++ b/test/libraries/ProtocolFeeLibrary.t.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {LPFeeLibrary} from "../../src/libraries/LPFeeLibrary.sol"; +import {ProtocolFeeLibrary} from "../../src/libraries/ProtocolFeeLibrary.sol"; + +contract ProtocolFeeLibraryTest is Test, GasSnapshot { + function test_getZeroForOneFee() public pure { + uint24 fee = uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE - 1) << 12 | ProtocolFeeLibrary.MAX_PROTOCOL_FEE; + assertEq(ProtocolFeeLibrary.getZeroForOneFee(fee), uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE)); + } + + function test_fuzz_getZeroForOneFee(uint24 fee) public pure { + assertEq(ProtocolFeeLibrary.getZeroForOneFee(fee), fee % 4096); + } + + function test_getOneForZeroFee() public pure { + uint24 fee = uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE - 1) << 12 | ProtocolFeeLibrary.MAX_PROTOCOL_FEE; + assertEq(ProtocolFeeLibrary.getOneForZeroFee(fee), uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE - 1)); + } + + function test_fuzz_getOneForZeroFee(uint24 fee) public pure { + assertEq(ProtocolFeeLibrary.getOneForZeroFee(fee), fee >> 12); + } + + function test_isValidProtocolFee_fee() public pure { + uint24 fee = uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE + 1) << 12 | ProtocolFeeLibrary.MAX_PROTOCOL_FEE; + assertFalse(ProtocolFeeLibrary.validate(fee)); + + fee = uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE) << 12 | (ProtocolFeeLibrary.MAX_PROTOCOL_FEE + 1); + assertFalse(ProtocolFeeLibrary.validate(fee)); + + fee = uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE + 1) << 12 | (ProtocolFeeLibrary.MAX_PROTOCOL_FEE + 1); + assertFalse(ProtocolFeeLibrary.validate(fee)); + + fee = uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE) << 12 | ProtocolFeeLibrary.MAX_PROTOCOL_FEE; + assertTrue(ProtocolFeeLibrary.validate(fee)); + + fee = uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE - 1) << 12 | ProtocolFeeLibrary.MAX_PROTOCOL_FEE - 1; + assertTrue(ProtocolFeeLibrary.validate(fee)); + + fee = uint24(0) << 12 | uint24(0); + assertTrue(ProtocolFeeLibrary.validate(fee)); + } + + function testFuzz_isValid(uint24 fee) public pure { + if ((fee >> 12 > ProtocolFeeLibrary.MAX_PROTOCOL_FEE) || (fee % 4096 > ProtocolFeeLibrary.MAX_PROTOCOL_FEE)) { + assertFalse(ProtocolFeeLibrary.validate(fee)); + } else { + assertTrue(ProtocolFeeLibrary.validate(fee)); + } + } + + function test_calculateSwapFee() public pure { + assertEq( + ProtocolFeeLibrary.calculateSwapFee( + uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE), LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE + ), + LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE + ); + assertEq(ProtocolFeeLibrary.calculateSwapFee(uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE), 3000), 3997); + assertEq( + ProtocolFeeLibrary.calculateSwapFee(uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE), 0), + ProtocolFeeLibrary.MAX_PROTOCOL_FEE + ); + assertEq(ProtocolFeeLibrary.calculateSwapFee(0, 0), 0); + assertEq(ProtocolFeeLibrary.calculateSwapFee(0, 1000), 1000); + } + + function test_fuzz_calculateSwapFee(uint24 protocolFee, uint24 lpFee) public pure { + protocolFee = uint24(bound(protocolFee, 0, ProtocolFeeLibrary.MAX_PROTOCOL_FEE)); + lpFee = uint24(bound(lpFee, 0, LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE)); + uint24 swapFee = ProtocolFeeLibrary.calculateSwapFee(protocolFee, lpFee); + // if lp fee is not the max, the swap fee should never be the max since the protocol fee is taken off first and then the lp fee is taken from the remaining amount + if (lpFee < LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE) { + assertLt(swapFee, LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE); + } + assertGe(swapFee, lpFee); + + uint256 expectedSwapFee = protocolFee + + lpFee * uint256(LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE - protocolFee) / LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE; + assertEq(swapFee, uint24(expectedSwapFee)); + } +} diff --git a/test/pool-cl/libraries/TickBitmap.t.sol b/test/pool-cl/libraries/TickBitmap.t.sol index 0102e783..6de727bd 100644 --- a/test/pool-cl/libraries/TickBitmap.t.sol +++ b/test/pool-cl/libraries/TickBitmap.t.sol @@ -23,6 +23,19 @@ contract TickBitmapTest is Test, GasSnapshot { } } + function testFuzz_compress(int24 tick, int24 tickSpacing) public pure { + tickSpacing = int24(bound(tickSpacing, 1, type(int24).max)); + int24 compressed = tick / tickSpacing; + if (tick < 0 && tick % tickSpacing != 0) compressed--; + assertEq(TickBitmap.compress(tick, tickSpacing), compressed); + } + + function testFuzz_position(int24 tick) public pure { + (int16 wordPos, uint8 bitPos) = TickBitmap.position(tick); + assertEq(wordPos, tick >> 8); + assertEq(bitPos, uint8(int8(tick % 256))); + } + function test_isInitialized_isFalseAtFirst() public { assertEq(isInitialized(1), false); } @@ -97,6 +110,22 @@ contract TickBitmapTest is Test, GasSnapshot { snapEnd(); } + function testFuzz_flipTick(int24 tick, int24 tickSpacing) public { + tickSpacing = int24(bound(tickSpacing, 1, type(int24).max)); + + if (tick % tickSpacing != 0) { + vm.expectRevert(abi.encodeWithSelector(TickBitmap.TickMisaligned.selector, tick, tickSpacing)); + bitmap.flipTick(tick, tickSpacing); + } else { + bool initialized = isInitialized(tick, tickSpacing); + bitmap.flipTick(tick, tickSpacing); + assertEq(isInitialized(tick, tickSpacing), !initialized); + // flip again + bitmap.flipTick(tick, tickSpacing); + assertEq(isInitialized(tick, tickSpacing), initialized); + } + } + function test_nextInitializedTickWithinOneWord_lteFalse_returnsTickToRightIfAtInitializedTick() public { (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(78, 1, false); assertEq(next, 84); @@ -282,6 +311,14 @@ contract TickBitmapTest is Test, GasSnapshot { } } + function isInitialized(int24 tick, int24 tickSpacing) internal view returns (bool) { + unchecked { + if (tick % tickSpacing != 0) return false; + (int16 wordPos, uint8 bitPos) = TickBitmap.position(tick / tickSpacing); + return bitmap[wordPos] & (1 << bitPos) != 0; + } + } + function isInitialized(int24 tick) internal view returns (bool) { (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, true); return next == tick ? initialized : false; diff --git a/test/pool-cl/libraries/TickMath.t.sol b/test/pool-cl/libraries/TickMath.t.sol index bb643c92..aeedc899 100644 --- a/test/pool-cl/libraries/TickMath.t.sol +++ b/test/pool-cl/libraries/TickMath.t.sol @@ -28,14 +28,14 @@ contract TickMathTestTest is Test { delete getTickAtSqrtRatioFuzzResults; } - function test_MIN_TICK_equalsNegativeMAX_TICK() public { + function test_MIN_TICK_equalsNegativeMAX_TICK() public view { // this invariant is required in the Tick#tickSpacingToMaxLiquidityPerTick formula int24 minTick = tickMath.MIN_TICK(); assertEq(minTick, tickMath.MAX_TICK() * -1); assertEq(minTick, MIN_TICK); } - function test_MAX_TICK_equalsNegativeMIN_TICK() public { + function test_MAX_TICK_equalsNegativeMIN_TICK() public view { // this invariant is required in the Tick#tickSpacingToMaxLiquidityPerTick formula // this test is redundant with the above MIN_TICK test int24 maxTick = tickMath.MAX_TICK(); @@ -43,6 +43,11 @@ contract TickMathTestTest is Test { assertEq(maxTick, MAX_TICK); } + function test_getSqrtRatioAtTick_throwsForInt24Min() public { + vm.expectRevert(TickMath.InvalidTick.selector); + tickMath.getSqrtRatioAtTick(type(int24).min); + } + function test_getSqrtRatioAtTick_throwsForTooLow() public { vm.expectRevert(TickMath.InvalidTick.selector); tickMath.getSqrtRatioAtTick(MIN_TICK - 1); @@ -53,32 +58,42 @@ contract TickMathTestTest is Test { tickMath.getSqrtRatioAtTick(MAX_TICK + 1); } - function test_getSqrtRatioAtTick_isValidMinTick() public { + function testFuzz_getSqrtRatioAtTick_throwsForTooLarge(int24 tick) public { + if (tick > 0) { + tick = int24(bound(tick, MAX_TICK + 1, type(int24).max)); + } else { + tick = int24(bound(tick, type(int24).min, MIN_TICK - 1)); + } + vm.expectRevert(TickMath.InvalidTick.selector); + tickMath.getSqrtRatioAtTick(tick); + } + + function test_getSqrtRatioAtTick_isValidMinTick() public view { assertEq(tickMath.getSqrtRatioAtTick(MIN_TICK), tickMath.MIN_SQRT_RATIO()); assertEq(tickMath.getSqrtRatioAtTick(MIN_TICK), 4295128739); } - function test_getSqrtRatioAtTick_isValidMinTickAddOne() public { + function test_getSqrtRatioAtTick_isValidMinTickAddOne() public view { assertEq(tickMath.getSqrtRatioAtTick(MIN_TICK + 1), 4295343490); } - function test_getSqrtRatioAtTick_isValidMaxTick() public { + function test_getSqrtRatioAtTick_isValidMaxTick() public view { assertEq(tickMath.getSqrtRatioAtTick(MAX_TICK), tickMath.MAX_SQRT_RATIO()); assertEq(tickMath.getSqrtRatioAtTick(MAX_TICK), 1461446703485210103287273052203988822378723970342); } - function test_getSqrtRatioAtTick_isValidMaxTickSubOne() public { + function test_getSqrtRatioAtTick_isValidMaxTickSubOne() public view { assertEq(tickMath.getSqrtRatioAtTick(MAX_TICK - 1), 1461373636630004318706518188784493106690254656249); } - function test_getSqrtRatioAtTick_isLessThanJSImplMinTick() public { + function test_getSqrtRatioAtTick_isLessThanJSImplMinTick() public view { // sqrt(1 / 2 ** 127) * 2 ** 96 uint160 jsMinSqrtRatio = 6085630636; uint160 solMinSqrtRatio = tickMath.getSqrtRatioAtTick(MIN_TICK); assertLt(solMinSqrtRatio, jsMinSqrtRatio); } - function test_getSqrtRatioAtTick_isGreaterThanJSImplMaxTick() public { + function test_getSqrtRatioAtTick_isGreaterThanJSImplMaxTick() public view { // sqrt(2 ** 127) * 2 ** 96 uint160 jsMaxSqrtRatio = 1033437718471923706666374484006904511252097097914; uint160 solMaxSqrtRatio = tickMath.getSqrtRatioAtTick(MAX_TICK); @@ -95,19 +110,29 @@ contract TickMathTestTest is Test { tickMath.getTickAtSqrtRatio(MAX_SQRT_RATIO + 1); } - function test_getTickAtSqrtRatio_isValidMinSqrtRatio() public { + function testFuzz_getTickAtSqrtPrice_throwsForInvalid(uint160 sqrtPriceX96, bool gte) public { + if (gte) { + sqrtPriceX96 = uint160(bound(sqrtPriceX96, MAX_SQRT_RATIO, type(uint160).max)); + } else { + sqrtPriceX96 = uint160(bound(sqrtPriceX96, 0, MIN_SQRT_RATIO - 1)); + } + vm.expectRevert(TickMath.InvalidSqrtRatio.selector); + tickMath.getTickAtSqrtRatio(sqrtPriceX96); + } + + function test_getTickAtSqrtRatio_isValidMinSqrtRatio() public view { assertEq(tickMath.getTickAtSqrtRatio(MIN_SQRT_RATIO), MIN_TICK); } - function test_getTickAtSqrtRatio_isValidMinSqrtRatioPlusOne() public { + function test_getTickAtSqrtRatio_isValidMinSqrtRatioPlusOne() public view { assertEq(tickMath.getTickAtSqrtRatio(4295343490), MIN_TICK + 1); } - function test_getTickAtSqrtRatio_isValidRatioClosestToMaxTick() public { + function test_getTickAtSqrtRatio_isValidRatioClosestToMaxTick() public view { assertEq(tickMath.getTickAtSqrtRatio(MAX_SQRT_RATIO - 1), MAX_TICK - 1); } - function test_getTickAtSqrtRatio_isValidMaxSqrtRatioMinusOne() public { + function test_getTickAtSqrtRatio_isValidMaxSqrtRatioMinusOne() public view { assertEq(tickMath.getTickAtSqrtRatio(1461373636630004318706518188784493106690254656249), MAX_TICK - 1); }