diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index a57d22ba0..b8989b888 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -265311 \ No newline at end of file +265373 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with native token.snap b/.forge-snapshots/addLiquidity with native token.snap index 1be361e02..d447a901c 100644 --- a/.forge-snapshots/addLiquidity with native token.snap +++ b/.forge-snapshots/addLiquidity with native token.snap @@ -1 +1 @@ -140154 \ No newline at end of file +140136 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity.snap b/.forge-snapshots/addLiquidity.snap index 993394194..a4f8803b5 100644 --- a/.forge-snapshots/addLiquidity.snap +++ b/.forge-snapshots/addLiquidity.snap @@ -1 +1 @@ -145471 \ No newline at end of file +145453 \ No newline at end of file diff --git a/.forge-snapshots/donate gas with 1 token.snap b/.forge-snapshots/donate gas with 1 token.snap index 7c4e06da7..5999220c5 100644 --- a/.forge-snapshots/donate gas with 1 token.snap +++ b/.forge-snapshots/donate gas with 1 token.snap @@ -1 +1 @@ -101145 \ No newline at end of file +101049 \ No newline at end of file diff --git a/.forge-snapshots/donate gas with 2 tokens.snap b/.forge-snapshots/donate gas with 2 tokens.snap index 6423ce1f4..bef388da8 100644 --- a/.forge-snapshots/donate gas with 2 tokens.snap +++ b/.forge-snapshots/donate gas with 2 tokens.snap @@ -1 +1 @@ -132135 \ No newline at end of file +132039 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index 39c68e33e..5d39173db 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -22974 \ No newline at end of file +22962 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with empty hook.snap b/.forge-snapshots/removeLiquidity with empty hook.snap index c94433f36..60c50e8f3 100644 --- a/.forge-snapshots/removeLiquidity with empty hook.snap +++ b/.forge-snapshots/removeLiquidity with empty hook.snap @@ -1 +1 @@ -56002 \ No newline at end of file +55984 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with native token.snap b/.forge-snapshots/removeLiquidity with native token.snap index 36e50fa2b..91d24dd8f 100644 --- a/.forge-snapshots/removeLiquidity with native token.snap +++ b/.forge-snapshots/removeLiquidity with native token.snap @@ -1 +1 @@ -148188 \ No newline at end of file +148170 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity.snap b/.forge-snapshots/removeLiquidity.snap index 245df4831..b5f49cf1d 100644 --- a/.forge-snapshots/removeLiquidity.snap +++ b/.forge-snapshots/removeLiquidity.snap @@ -1 +1 @@ -149652 \ No newline at end of file +149634 \ No newline at end of file diff --git a/.forge-snapshots/simple swap with native.snap b/.forge-snapshots/simple swap with native.snap index 797d10395..11e0aff71 100644 --- a/.forge-snapshots/simple swap with native.snap +++ b/.forge-snapshots/simple swap with native.snap @@ -1 +1 @@ -132905 \ No newline at end of file +132814 \ No newline at end of file diff --git a/.forge-snapshots/simple swap.snap b/.forge-snapshots/simple swap.snap index 483955831..51754821b 100644 --- a/.forge-snapshots/simple swap.snap +++ b/.forge-snapshots/simple swap.snap @@ -1 +1 @@ -146781 \ No newline at end of file +146690 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index 6671e8870..3ea3bdda3 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -72364 \ No newline at end of file +72273 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index c31794dbd..bb8dceb17 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -60367 \ No newline at end of file +60276 \ No newline at end of file diff --git a/.forge-snapshots/swap burn 6909 for input.snap b/.forge-snapshots/swap burn 6909 for input.snap index 4cc58cdca..2c5d23970 100644 --- a/.forge-snapshots/swap burn 6909 for input.snap +++ b/.forge-snapshots/swap burn 6909 for input.snap @@ -1 +1 @@ -80397 \ No newline at end of file +80306 \ No newline at end of file diff --git a/.forge-snapshots/swap burn native 6909 for input.snap b/.forge-snapshots/swap burn native 6909 for input.snap index e67a9a599..7cf446276 100644 --- a/.forge-snapshots/swap burn native 6909 for input.snap +++ b/.forge-snapshots/swap burn native 6909 for input.snap @@ -1 +1 @@ -76394 \ No newline at end of file +76303 \ No newline at end of file diff --git a/.forge-snapshots/swap mint native output as 6909.snap b/.forge-snapshots/swap mint native output as 6909.snap index f504a2867..ef5b6e1ca 100644 --- a/.forge-snapshots/swap mint native output as 6909.snap +++ b/.forge-snapshots/swap mint native output as 6909.snap @@ -1 +1 @@ -138745 \ No newline at end of file +138654 \ No newline at end of file diff --git a/.forge-snapshots/swap mint output as 6909.snap b/.forge-snapshots/swap mint output as 6909.snap index d2db9e035..b4ba009f6 100644 --- a/.forge-snapshots/swap mint output as 6909.snap +++ b/.forge-snapshots/swap mint output as 6909.snap @@ -1 +1 @@ -155596 \ No newline at end of file +155505 \ No newline at end of file diff --git a/.forge-snapshots/swap with dynamic fee.snap b/.forge-snapshots/swap with dynamic fee.snap index a901423df..34409c289 100644 --- a/.forge-snapshots/swap with dynamic fee.snap +++ b/.forge-snapshots/swap with dynamic fee.snap @@ -1 +1 @@ -89671 \ No newline at end of file +89580 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index 29498b964..14da3b121 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -60345 \ No newline at end of file +60254 \ No newline at end of file diff --git a/.forge-snapshots/update dynamic fee in before swap.snap b/.forge-snapshots/update dynamic fee in before swap.snap index d6bca4c74..ac74b3045 100644 --- a/.forge-snapshots/update dynamic fee in before swap.snap +++ b/.forge-snapshots/update dynamic fee in before swap.snap @@ -1 +1 @@ -140297 \ No newline at end of file +140246 \ No newline at end of file diff --git a/src/libraries/Hooks.sol b/src/libraries/Hooks.sol index 77873154e..6fed8bea9 100644 --- a/src/libraries/Hooks.sol +++ b/src/libraries/Hooks.sol @@ -71,13 +71,13 @@ library Hooks { } /// @notice Ensures that the hook address includes at least one hook flag or dynamic fees, or is the 0 address - /// @param hook The hook to verify - function isValidHookAddress(IHooks hook, uint24 fee) internal pure returns (bool) { + /// @param self The hook to verify + function isValidHookAddress(IHooks self, uint24 fee) internal pure returns (bool) { // If there is no hook contract set, then fee cannot be dynamic // If a hook contract is set, it must have at least 1 flag set, or have a dynamic fee - return address(hook) == address(0) + return address(self) == address(0) ? !fee.isDynamicFee() - : (uint160(address(hook)) >= AFTER_DONATE_FLAG || fee.isDynamicFee()); + : (uint160(address(self)) >= AFTER_DONATE_FLAG || fee.isDynamicFee()); } /// @notice performs a hook call using the given calldata on the given hook @@ -96,6 +96,7 @@ library Hooks { /// @notice performs a hook call using the given calldata on the given hook function callHook(IHooks self, bytes memory data) internal { + if (msg.sender == address(self)) return; (bytes4 expectedSelector, bytes4 selector) = _callHook(self, data); if (selector != expectedSelector) { @@ -132,9 +133,9 @@ library Hooks { IPoolManager.ModifyLiquidityParams memory params, bytes calldata hookData ) internal { - if (params.liquidityDelta > 0 && key.hooks.hasPermission(BEFORE_ADD_LIQUIDITY_FLAG)) { + if (params.liquidityDelta > 0 && self.hasPermission(BEFORE_ADD_LIQUIDITY_FLAG)) { self.callHook(abi.encodeWithSelector(IHooks.beforeAddLiquidity.selector, msg.sender, key, params, hookData)); - } else if (params.liquidityDelta <= 0 && key.hooks.hasPermission(BEFORE_REMOVE_LIQUIDITY_FLAG)) { + } else if (params.liquidityDelta <= 0 && self.hasPermission(BEFORE_REMOVE_LIQUIDITY_FLAG)) { self.callHook( abi.encodeWithSelector(IHooks.beforeRemoveLiquidity.selector, msg.sender, key, params, hookData) ); @@ -149,11 +150,11 @@ library Hooks { BalanceDelta delta, bytes calldata hookData ) internal { - if (params.liquidityDelta > 0 && key.hooks.hasPermission(AFTER_ADD_LIQUIDITY_FLAG)) { + if (params.liquidityDelta > 0 && self.hasPermission(AFTER_ADD_LIQUIDITY_FLAG)) { self.callHook( abi.encodeWithSelector(IHooks.afterAddLiquidity.selector, msg.sender, key, params, delta, hookData) ); - } else if (params.liquidityDelta <= 0 && key.hooks.hasPermission(AFTER_REMOVE_LIQUIDITY_FLAG)) { + } else if (params.liquidityDelta <= 0 && self.hasPermission(AFTER_REMOVE_LIQUIDITY_FLAG)) { self.callHook( abi.encodeWithSelector(IHooks.afterRemoveLiquidity.selector, msg.sender, key, params, delta, hookData) ); @@ -164,7 +165,7 @@ library Hooks { function beforeSwap(IHooks self, PoolKey memory key, IPoolManager.SwapParams memory params, bytes calldata hookData) internal { - if (key.hooks.hasPermission(BEFORE_SWAP_FLAG)) { + if (self.hasPermission(BEFORE_SWAP_FLAG)) { self.callHook(abi.encodeWithSelector(IHooks.beforeSwap.selector, msg.sender, key, params, hookData)); } } @@ -177,7 +178,7 @@ library Hooks { BalanceDelta delta, bytes calldata hookData ) internal { - if (key.hooks.hasPermission(AFTER_SWAP_FLAG)) { + if (self.hasPermission(AFTER_SWAP_FLAG)) { self.callHook(abi.encodeWithSelector(IHooks.afterSwap.selector, msg.sender, key, params, delta, hookData)); } } @@ -186,7 +187,7 @@ library Hooks { function beforeDonate(IHooks self, PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData) internal { - if (key.hooks.hasPermission(BEFORE_DONATE_FLAG)) { + if (self.hasPermission(BEFORE_DONATE_FLAG)) { self.callHook( abi.encodeWithSelector(IHooks.beforeDonate.selector, msg.sender, key, amount0, amount1, hookData) ); @@ -197,7 +198,7 @@ library Hooks { function afterDonate(IHooks self, PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData) internal { - if (key.hooks.hasPermission(AFTER_DONATE_FLAG)) { + if (self.hasPermission(AFTER_DONATE_FLAG)) { self.callHook( abi.encodeWithSelector(IHooks.afterDonate.selector, msg.sender, key, amount0, amount1, hookData) ); diff --git a/src/test/PoolDonateTest.sol b/src/test/PoolDonateTest.sol index d568c32e0..55d95c392 100644 --- a/src/test/PoolDonateTest.sol +++ b/src/test/PoolDonateTest.sol @@ -43,23 +43,17 @@ contract PoolDonateTest is PoolTestBase { CallbackData memory data = abi.decode(rawData, (CallbackData)); - (, uint256 poolBalanceBefore0, int256 deltaBefore0) = - _fetchBalances(data.key.currency0, data.sender, address(this)); - (, uint256 poolBalanceBefore1, int256 deltaBefore1) = - _fetchBalances(data.key.currency1, data.sender, address(this)); + (,, int256 deltaBefore0) = _fetchBalances(data.key.currency0, data.sender, address(this)); + (,, int256 deltaBefore1) = _fetchBalances(data.key.currency1, data.sender, address(this)); require(deltaBefore0 == 0, "deltaBefore0 is not 0"); require(deltaBefore1 == 0, "deltaBefore1 is not 0"); BalanceDelta delta = manager.donate(data.key, data.amount0, data.amount1, data.hookData); - (, uint256 poolBalanceAfter0, int256 deltaAfter0) = - _fetchBalances(data.key.currency0, data.sender, address(this)); - (, uint256 poolBalanceAfter1, int256 deltaAfter1) = - _fetchBalances(data.key.currency1, data.sender, address(this)); + (,, int256 deltaAfter0) = _fetchBalances(data.key.currency0, data.sender, address(this)); + (,, int256 deltaAfter1) = _fetchBalances(data.key.currency1, data.sender, address(this)); - require(poolBalanceBefore0 == poolBalanceAfter0, "poolBalanceBefore0 is not equal to poolBalanceAfter0"); - require(poolBalanceBefore1 == poolBalanceAfter1, "poolBalanceBefore1 is not equal to poolBalanceAfter1"); require(deltaAfter0 == -int256(data.amount0), "deltaAfter0 is not equal to -int256(data.amount0)"); require(deltaAfter1 == -int256(data.amount1), "deltaAfter1 is not equal to -int256(data.amount1)"); diff --git a/src/test/PoolSwapTest.sol b/src/test/PoolSwapTest.sol index 0817017b3..255fb678a 100644 --- a/src/test/PoolSwapTest.sol +++ b/src/test/PoolSwapTest.sol @@ -52,23 +52,16 @@ contract PoolSwapTest is PoolTestBase { CallbackData memory data = abi.decode(rawData, (CallbackData)); - (, uint256 poolBalanceBefore0, int256 deltaBefore0) = - _fetchBalances(data.key.currency0, data.sender, address(this)); - (, uint256 poolBalanceBefore1, int256 deltaBefore1) = - _fetchBalances(data.key.currency1, data.sender, address(this)); + (,, int256 deltaBefore0) = _fetchBalances(data.key.currency0, data.sender, address(this)); + (,, int256 deltaBefore1) = _fetchBalances(data.key.currency1, data.sender, address(this)); require(deltaBefore0 == 0, "deltaBefore0 is not equal to 0"); require(deltaBefore1 == 0, "deltaBefore1 is not equal to 0"); BalanceDelta delta = manager.swap(data.key, data.params, data.hookData); - (, uint256 poolBalanceAfter0, int256 deltaAfter0) = - _fetchBalances(data.key.currency0, data.sender, address(this)); - (, uint256 poolBalanceAfter1, int256 deltaAfter1) = - _fetchBalances(data.key.currency1, data.sender, address(this)); - - require(poolBalanceBefore0 == poolBalanceAfter0, "poolBalanceBefore0 is not equal to poolBalanceAfter0"); - require(poolBalanceBefore1 == poolBalanceAfter1, "poolBalanceBefore1 is not equal to poolBalanceAfter1"); + (,, int256 deltaAfter0) = _fetchBalances(data.key.currency0, data.sender, address(this)); + (,, int256 deltaAfter1) = _fetchBalances(data.key.currency1, data.sender, address(this)); if (data.params.zeroForOne) { if (data.params.amountSpecified < 0) { diff --git a/src/test/SkipCallsTestHook.sol b/src/test/SkipCallsTestHook.sol new file mode 100644 index 000000000..0ebc72185 --- /dev/null +++ b/src/test/SkipCallsTestHook.sol @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Hooks} from "../libraries/Hooks.sol"; +import {BaseTestHooks} from "./BaseTestHooks.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {PoolId, PoolIdLibrary} from "../types/PoolId.sol"; +import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol"; +import {CurrencyLibrary, Currency} from "../types/Currency.sol"; +import {PoolTestBase} from "./PoolTestBase.sol"; +import {Constants} from "../../test/utils/Constants.sol"; +import {Test} from "forge-std/Test.sol"; + +contract SkipCallsTestHook is BaseTestHooks, Test { + using PoolIdLibrary for PoolKey; + using Hooks for IHooks; + + uint256 public counter; + IPoolManager manager; + + function setManager(IPoolManager _manager) external { + manager = _manager; + } + + function beforeInitialize(address, PoolKey calldata key, uint160 sqrtPriceX96, bytes calldata hookData) + external + override + returns (bytes4) + { + counter++; + _initialize(key, sqrtPriceX96, hookData); + return IHooks.beforeInitialize.selector; + } + + function afterInitialize(address, PoolKey calldata key, uint160 sqrtPriceX96, int24, bytes calldata hookData) + external + override + returns (bytes4) + { + counter++; + _initialize(key, sqrtPriceX96, hookData); + return IHooks.afterInitialize.selector; + } + + function beforeAddLiquidity( + address, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external override returns (bytes4) { + counter++; + _addLiquidity(key, params, hookData); + return IHooks.beforeAddLiquidity.selector; + } + + function afterAddLiquidity( + address, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata params, + BalanceDelta, + bytes calldata hookData + ) external override returns (bytes4) { + counter++; + _addLiquidity(key, params, hookData); + return IHooks.afterAddLiquidity.selector; + } + + function beforeRemoveLiquidity( + address, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external override returns (bytes4) { + counter++; + _removeLiquidity(key, params, hookData); + return IHooks.beforeRemoveLiquidity.selector; + } + + function afterRemoveLiquidity( + address, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata params, + BalanceDelta, + bytes calldata hookData + ) external override returns (bytes4) { + counter++; + _removeLiquidity(key, params, hookData); + return IHooks.afterRemoveLiquidity.selector; + } + + function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata hookData) + external + override + returns (bytes4) + { + counter++; + _swap(key, params, hookData); + return IHooks.beforeSwap.selector; + } + + function afterSwap( + address, + PoolKey calldata key, + IPoolManager.SwapParams calldata params, + BalanceDelta, + bytes calldata hookData + ) external override returns (bytes4) { + counter++; + _swap(key, params, hookData); + return IHooks.afterSwap.selector; + } + + function beforeDonate(address, PoolKey calldata key, uint256 amt0, uint256 amt1, bytes calldata hookData) + external + override + returns (bytes4) + { + counter++; + _donate(key, amt0, amt1, hookData); + return IHooks.beforeDonate.selector; + } + + function afterDonate(address, PoolKey calldata key, uint256 amt0, uint256 amt1, bytes calldata hookData) + external + override + returns (bytes4) + { + counter++; + _donate(key, amt0, amt1, hookData); + return IHooks.afterDonate.selector; + } + + function _initialize(PoolKey memory key, uint160 sqrtPriceX96, bytes calldata hookData) public { + // initialize a new pool with different fee + key.fee = 2000; + IPoolManager(manager).initialize(key, sqrtPriceX96, hookData); + } + + function _swap(PoolKey calldata key, IPoolManager.SwapParams memory params, bytes calldata hookData) public { + IPoolManager(manager).swap(key, params, hookData); + address payer = abi.decode(hookData, (address)); + int256 delta0 = IPoolManager(manager).currencyDelta(address(this), key.currency0); + assertEq(delta0, params.amountSpecified); + int256 delta1 = IPoolManager(manager).currencyDelta(address(this), key.currency1); + assert(delta1 > 0); + IERC20Minimal(Currency.unwrap(key.currency0)).transferFrom(payer, address(manager), uint256(-delta0)); + manager.settle(key.currency0); + manager.take(key.currency1, payer, uint256(delta1)); + } + + function _addLiquidity( + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams memory params, + bytes calldata hookData + ) public { + IPoolManager(manager).modifyLiquidity(key, params, hookData); + address payer = abi.decode(hookData, (address)); + int256 delta0 = IPoolManager(manager).currencyDelta(address(this), key.currency0); + int256 delta1 = IPoolManager(manager).currencyDelta(address(this), key.currency1); + + assert(delta0 < 0 || delta1 < 0); + assert(!(delta0 > 0 || delta1 > 0)); + + IERC20Minimal(Currency.unwrap(key.currency0)).transferFrom(payer, address(manager), uint256(-delta0)); + manager.settle(key.currency0); + IERC20Minimal(Currency.unwrap(key.currency1)).transferFrom(payer, address(manager), uint256(-delta1)); + manager.settle(key.currency1); + } + + function _removeLiquidity( + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams memory params, + bytes calldata hookData + ) public { + // first hook needs to add liquidity for itself + IPoolManager.ModifyLiquidityParams memory newParams = + IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18}); + IPoolManager(manager).modifyLiquidity(key, newParams, hookData); + // hook removes liquidity + IPoolManager(manager).modifyLiquidity(key, params, hookData); + address payer = abi.decode(hookData, (address)); + int256 delta0 = IPoolManager(manager).currencyDelta(address(this), key.currency0); + int256 delta1 = IPoolManager(manager).currencyDelta(address(this), key.currency1); + + assert(delta0 < 0 || delta1 < 0); + assert(!(delta0 > 0 || delta1 > 0)); + + IERC20Minimal(Currency.unwrap(key.currency0)).transferFrom(payer, address(manager), uint256(-delta0)); + manager.settle(key.currency0); + IERC20Minimal(Currency.unwrap(key.currency1)).transferFrom(payer, address(manager), uint256(-delta1)); + manager.settle(key.currency1); + } + + function _donate(PoolKey calldata key, uint256 amt0, uint256 amt1, bytes calldata hookData) public { + IPoolManager(manager).donate(key, amt0, amt1, hookData); + address payer = abi.decode(hookData, (address)); + int256 delta0 = IPoolManager(manager).currencyDelta(address(this), key.currency0); + int256 delta1 = IPoolManager(manager).currencyDelta(address(this), key.currency1); + IERC20Minimal(Currency.unwrap(key.currency0)).transferFrom(payer, address(manager), uint256(-delta0)); + IERC20Minimal(Currency.unwrap(key.currency1)).transferFrom(payer, address(manager), uint256(-delta1)); + manager.settle(key.currency0); + manager.settle(key.currency1); + } +} diff --git a/test/ERC6909Claims.t.sol b/test/ERC6909Claims.t.sol index fc01cde5a..dbf3dc478 100644 --- a/test/ERC6909Claims.t.sol +++ b/test/ERC6909Claims.t.sol @@ -31,7 +31,11 @@ contract ERC6909ClaimsTest is Test { if (mintAmount == type(uint256).max) { assertEq(token.allowance(sender, address(this), id), type(uint256).max); } else { - assertEq(token.allowance(sender, address(this), id), mintAmount - transferAmount); + if (sender != address(this)) { + assertEq(token.allowance(sender, address(this), id), mintAmount - transferAmount); + } else { + assertEq(token.allowance(sender, address(this), id), mintAmount); + } } assertEq(token.balanceOf(sender, id), mintAmount - transferAmount); } @@ -255,7 +259,11 @@ contract ERC6909ClaimsTest is Test { if (mintAmount == type(uint256).max) { assertEq(token.allowance(sender, address(this), id), type(uint256).max); } else { - assertEq(token.allowance(sender, address(this), id), mintAmount - transferAmount); + if (sender != address(this)) { + assertEq(token.allowance(sender, address(this), id), mintAmount - transferAmount); + } else { + assertEq(token.allowance(sender, address(this), id), mintAmount); + } } if (sender == receiver) { @@ -367,6 +375,7 @@ contract ERC6909ClaimsTest is Test { token.mint(sender, id, amount); + vm.assume(sender != address(this)); token.transferFrom(sender, receiver, id, amount); } } diff --git a/test/SkipCallsTestHook.t.sol b/test/SkipCallsTestHook.t.sol new file mode 100644 index 000000000..d12609b0f --- /dev/null +++ b/test/SkipCallsTestHook.t.sol @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {PoolId, PoolIdLibrary} from "../src/types/PoolId.sol"; +import {Hooks} from "../src/libraries/Hooks.sol"; +import {SwapFeeLibrary} from "../src/libraries/SwapFeeLibrary.sol"; +import {IPoolManager} from "../src/interfaces/IPoolManager.sol"; +import {IProtocolFees} from "../src/interfaces/IProtocolFees.sol"; +import {IHooks} from "../src/interfaces/IHooks.sol"; +import {PoolKey} from "../src/types/PoolKey.sol"; +import {PoolManager} from "../src/PoolManager.sol"; +import {PoolSwapTest} from "../src/test/PoolSwapTest.sol"; +import {Deployers} from "./utils/Deployers.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {Currency, CurrencyLibrary} from "../src/types/Currency.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {Constants} from "../test/utils/Constants.sol"; +import {SkipCallsTestHook} from "../src/test/SkipCallsTestHook.sol"; + +contract SkipCallsTest is Test, Deployers, GasSnapshot { + using PoolIdLibrary for PoolKey; + + IPoolManager.SwapParams swapParams = + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + + PoolSwapTest.TestSettings testSettings = + PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true, currencyAlreadySent: false}); + + uint160 clearAllHookPermisssionsMask; + uint256 hookPermissionCount = 10; + + function setUp() public { + clearAllHookPermisssionsMask = ~uint160(0) >> hookPermissionCount; + } + + function deploy(SkipCallsTestHook skipCallsTestHook) private { + SkipCallsTestHook impl = new SkipCallsTestHook(); + vm.etch(address(skipCallsTestHook), address(impl).code); + deployFreshManagerAndRouters(); + skipCallsTestHook.setManager(IPoolManager(manager)); + (currency0, currency1) = deployMintAndApprove2Currencies(); + + assertEq(skipCallsTestHook.counter(), 0); + + (key,) = initPool(currency0, currency1, IHooks(address(skipCallsTestHook)), 3000, SQRT_RATIO_1_1, ZERO_BYTES); + } + + function approveAndAddLiquidity(SkipCallsTestHook skipCallsTestHook) private { + MockERC20(Currency.unwrap(key.currency0)).approve(address(skipCallsTestHook), Constants.MAX_UINT256); + MockERC20(Currency.unwrap(key.currency1)).approve(address(skipCallsTestHook), Constants.MAX_UINT256); + modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, abi.encode(address(this))); + } + + function test_beforeInitialize_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address(uint160(type(uint160).max & clearAllHookPermisssionsMask | Hooks.BEFORE_INITIALIZE_FLAG)) + ); + + // initializes pool and increments counter + deploy(skipCallsTestHook); + assertEq(skipCallsTestHook.counter(), 1); + } + + function test_afterInitialize_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address(uint160(type(uint160).max & clearAllHookPermisssionsMask | Hooks.AFTER_INITIALIZE_FLAG)) + ); + + // initializes pool and increments counter + deploy(skipCallsTestHook); + assertEq(skipCallsTestHook.counter(), 1); + } + + function test_beforeAddLiquidity_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address(uint160(type(uint160).max & clearAllHookPermisssionsMask | Hooks.BEFORE_ADD_LIQUIDITY_FLAG)) + ); + + deploy(skipCallsTestHook); + assertEq(skipCallsTestHook.counter(), 0); + + // adds liquidity and increments counter + approveAndAddLiquidity(skipCallsTestHook); + assertEq(skipCallsTestHook.counter(), 1); + // adds liquidity again and increments counter + modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 2); + } + + function test_afterAddLiquidity_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address(uint160(type(uint160).max & clearAllHookPermisssionsMask | Hooks.AFTER_ADD_LIQUIDITY_FLAG)) + ); + + deploy(skipCallsTestHook); + assertEq(skipCallsTestHook.counter(), 0); + + // adds liquidity and increments counter + approveAndAddLiquidity(skipCallsTestHook); + assertEq(skipCallsTestHook.counter(), 1); + // adds liquidity and increments counter again + modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 2); + } + + function test_beforeRemoveLiquidity_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address(uint160(type(uint160).max & clearAllHookPermisssionsMask | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG)) + ); + + deploy(skipCallsTestHook); + approveAndAddLiquidity(skipCallsTestHook); + assertEq(skipCallsTestHook.counter(), 0); + + // removes liquidity and increments counter + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 1); + // adds liquidity again + modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, abi.encode(address(this))); + // removes liquidity again and increments counter + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 2); + } + + function test_afterRemoveLiquidity_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address(uint160(type(uint160).max & clearAllHookPermisssionsMask | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG)) + ); + + deploy(skipCallsTestHook); + approveAndAddLiquidity(skipCallsTestHook); + assertEq(skipCallsTestHook.counter(), 0); + + // removes liquidity and increments counter + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 1); + // adds liquidity again + modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, abi.encode(address(this))); + // removes liquidity again and increments counter + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 2); + } + + function test_beforeSwap_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address(uint160(type(uint160).max & clearAllHookPermisssionsMask | Hooks.BEFORE_SWAP_FLAG)) + ); + + deploy(skipCallsTestHook); + approveAndAddLiquidity(skipCallsTestHook); + assertEq(skipCallsTestHook.counter(), 0); + + // swaps and increments counter + swapRouter.swap(key, swapParams, testSettings, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 1); + // swaps again and increments counter + swapRouter.swap(key, swapParams, testSettings, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 2); + } + + function test_afterSwap_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address(uint160(type(uint160).max & clearAllHookPermisssionsMask | Hooks.AFTER_SWAP_FLAG)) + ); + + deploy(skipCallsTestHook); + approveAndAddLiquidity(skipCallsTestHook); + assertEq(skipCallsTestHook.counter(), 0); + + // swaps and increments counter + swapRouter.swap(key, swapParams, testSettings, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 1); + // swaps again and increments counter + swapRouter.swap(key, swapParams, testSettings, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 2); + } + + function test_beforeDonate_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address(uint160(type(uint160).max & clearAllHookPermisssionsMask | Hooks.BEFORE_DONATE_FLAG)) + ); + + deploy(skipCallsTestHook); + approveAndAddLiquidity(skipCallsTestHook); + assertEq(skipCallsTestHook.counter(), 0); + + // donates and increments counter + donateRouter.donate(key, 100, 200, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 1); + // donates again and increments counter + donateRouter.donate(key, 100, 200, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 2); + } + + function test_afterDonate_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address(uint160(type(uint160).max & clearAllHookPermisssionsMask | Hooks.AFTER_DONATE_FLAG)) + ); + + deploy(skipCallsTestHook); + approveAndAddLiquidity(skipCallsTestHook); + assertEq(skipCallsTestHook.counter(), 0); + + // donates and increments counter + donateRouter.donate(key, 100, 200, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 1); + // donates again and increments counter + donateRouter.donate(key, 100, 200, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 2); + } +}