diff --git a/.forge-snapshots/donate gas with 1 token.snap b/.forge-snapshots/donate gas with 1 token.snap index 522be2b14..846f87e6b 100644 --- a/.forge-snapshots/donate gas with 1 token.snap +++ b/.forge-snapshots/donate gas with 1 token.snap @@ -1 +1 @@ -131119 \ No newline at end of file +131041 \ 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 00283d71e..dd87206ae 100644 --- a/.forge-snapshots/donate gas with 2 tokens.snap +++ b/.forge-snapshots/donate gas with 2 tokens.snap @@ -1 +1 @@ -175942 \ No newline at end of file +175864 \ No newline at end of file diff --git a/src/test/PoolDonateTest.sol b/src/test/PoolDonateTest.sol index 0fad5786b..13fe95b2f 100644 --- a/src/test/PoolDonateTest.sol +++ b/src/test/PoolDonateTest.sol @@ -43,21 +43,17 @@ contract PoolDonateTest is PoolTestBase { CallbackData memory data = abi.decode(rawData, (CallbackData)); - (,, uint256 reserveBefore0, int256 deltaBefore0) = - _fetchBalances(data.key.currency0, data.sender, address(this)); - (,, uint256 reserveBefore1, 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 reserveAfter0, int256 deltaAfter0) = _fetchBalances(data.key.currency0, data.sender, address(this)); - (,, uint256 reserveAfter1, 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(reserveBefore0 == reserveAfter0, "reserveBefore0 is not equal to reserveAfter0"); - require(reserveBefore1 == reserveAfter1, "reserveBefore1 is not equal to reserveAfter1"); 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/SkipCallsTestHook.sol b/src/test/SkipCallsTestHook.sol index 800d8e84e..ea11ddaab 100644 --- a/src/test/SkipCallsTestHook.sol +++ b/src/test/SkipCallsTestHook.sol @@ -11,6 +11,7 @@ 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 { @@ -19,23 +20,129 @@ contract SkipCallsTestHook is BaseTestHooks, Test { uint256 public counter; IPoolManager manager; - uint24 internal fee; 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, + bytes calldata hookData + ) external override returns (bytes4) { + counter++; + _initialize(key, Constants.SQRT_RATIO_1_1, hookData); + return IHooks.beforeAddLiquidity.selector; + } + + function afterAddLiquidity( + address, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata, + BalanceDelta, + bytes calldata hookData + ) external override returns (bytes4) { + counter++; + _initialize(key, Constants.SQRT_RATIO_1_1, hookData); + return IHooks.afterAddLiquidity.selector; + } + + function beforeRemoveLiquidity( + address, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata, + bytes calldata hookData + ) external override returns (bytes4) { + counter++; + IPoolManager.ModifyLiquidityParams memory newParams = + IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 0.1e18}); + _modifyLiquidity(key, newParams, hookData); + return IHooks.beforeRemoveLiquidity.selector; + } + + function afterRemoveLiquidity( + address, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata, + BalanceDelta, + bytes calldata hookData + ) external override returns (bytes4) { + counter++; + IPoolManager.ModifyLiquidityParams memory newParams = + IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 0.1e18}); + _modifyLiquidity(key, newParams, hookData); + return IHooks.afterRemoveLiquidity.selector; + } + function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata hookData) external override returns (bytes4) { counter++; - callSwap(key, params, hookData); + _swap(key, params, hookData); return IHooks.beforeSwap.selector; } - function callSwap(PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata hookData) public { + 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 { + 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); @@ -46,4 +153,37 @@ contract SkipCallsTestHook is BaseTestHooks, Test { manager.settle(key.currency0); manager.take(key.currency1, payer, uint256(delta1)); } + + function _modifyLiquidity( + 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); + if (params.liquidityDelta < 0) { + assert(delta0 > 0 || delta1 > 0); + assert(!(delta0 < 0 || delta1 < 0)); + } else if (params.liquidityDelta > 0) { + 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/SkipCallsTestHook.t.sol b/test/SkipCallsTestHook.t.sol index d1dfdc3b5..f6e6178d9 100644 --- a/test/SkipCallsTestHook.t.sol +++ b/test/SkipCallsTestHook.t.sol @@ -22,9 +22,13 @@ import {SkipCallsTestHook} from "../src/test/SkipCallsTestHook.sol"; contract SkipCallsTest is Test, Deployers, GasSnapshot { using PoolIdLibrary for PoolKey; - SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook(address(uint160(Hooks.BEFORE_SWAP_FLAG))); + IPoolManager.SwapParams swapParams = + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - function setUp() public { + PoolSwapTest.TestSettings testSettings = + PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true, currencyAlreadySent: false}); + + function deploy(SkipCallsTestHook skipCallsTestHook) public { SkipCallsTestHook impl = new SkipCallsTestHook(); vm.etch(address(skipCallsTestHook), address(impl).code); deployFreshManagerAndRouters(); @@ -36,12 +40,152 @@ contract SkipCallsTest is Test, Deployers, GasSnapshot { ); } + function test_beforeInitialize_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address( + uint160(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF) + & uint160( + ~Hooks.AFTER_INITIALIZE_FLAG & ~Hooks.BEFORE_ADD_LIQUIDITY_FLAG & ~Hooks.AFTER_ADD_LIQUIDITY_FLAG + & ~Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG & ~Hooks.AFTER_REMOVE_LIQUIDITY_FLAG & ~Hooks.BEFORE_SWAP_FLAG + & ~Hooks.AFTER_SWAP_FLAG & ~Hooks.BEFORE_DONATE_FLAG & ~Hooks.AFTER_DONATE_FLAG + ) + ) + ); + + deploy(skipCallsTestHook); + + assertEq(skipCallsTestHook.counter(), 1); + } + + function test_afterInitialize_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address( + uint160(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF) + & uint160( + ~Hooks.BEFORE_INITIALIZE_FLAG & ~Hooks.BEFORE_ADD_LIQUIDITY_FLAG & ~Hooks.AFTER_ADD_LIQUIDITY_FLAG + & ~Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG & ~Hooks.AFTER_REMOVE_LIQUIDITY_FLAG & ~Hooks.BEFORE_SWAP_FLAG + & ~Hooks.AFTER_SWAP_FLAG & ~Hooks.BEFORE_DONATE_FLAG & ~Hooks.AFTER_DONATE_FLAG + ) + ) + ); + + deploy(skipCallsTestHook); + + assertEq(skipCallsTestHook.counter(), 1); + } + + function test_beforeAddLiquidity_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address( + uint160(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF) + & uint160( + ~Hooks.BEFORE_INITIALIZE_FLAG & ~Hooks.AFTER_INITIALIZE_FLAG & ~Hooks.AFTER_ADD_LIQUIDITY_FLAG + & ~Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG & ~Hooks.AFTER_REMOVE_LIQUIDITY_FLAG & ~Hooks.BEFORE_SWAP_FLAG + & ~Hooks.AFTER_SWAP_FLAG & ~Hooks.BEFORE_DONATE_FLAG & ~Hooks.AFTER_DONATE_FLAG + ) + ) + ); + + deploy(skipCallsTestHook); + + assertEq(skipCallsTestHook.counter(), 1); + } + + function test_afterAddLiquidity_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address( + uint160(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF) + & uint160( + ~Hooks.BEFORE_INITIALIZE_FLAG & ~Hooks.AFTER_INITIALIZE_FLAG & ~Hooks.BEFORE_ADD_LIQUIDITY_FLAG + & ~Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG & ~Hooks.AFTER_REMOVE_LIQUIDITY_FLAG & ~Hooks.BEFORE_SWAP_FLAG + & ~Hooks.AFTER_SWAP_FLAG & ~Hooks.BEFORE_DONATE_FLAG & ~Hooks.AFTER_DONATE_FLAG + ) + ) + ); + + deploy(skipCallsTestHook); + + assertEq(skipCallsTestHook.counter(), 1); + } + + function test_beforeRemoveLiquidity_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address( + uint160(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF) + & uint160( + ~Hooks.BEFORE_INITIALIZE_FLAG & ~Hooks.AFTER_INITIALIZE_FLAG & ~Hooks.BEFORE_ADD_LIQUIDITY_FLAG + & ~Hooks.AFTER_ADD_LIQUIDITY_FLAG & ~Hooks.AFTER_REMOVE_LIQUIDITY_FLAG & ~Hooks.BEFORE_SWAP_FLAG + & ~Hooks.AFTER_SWAP_FLAG & ~Hooks.BEFORE_DONATE_FLAG & ~Hooks.AFTER_DONATE_FLAG + ) + ) + ); + + deploy(skipCallsTestHook); + + 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, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 1); + } + + function test_afterRemoveLiquidity_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address( + uint160(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF) + & uint160( + ~Hooks.BEFORE_INITIALIZE_FLAG & ~Hooks.AFTER_INITIALIZE_FLAG & ~Hooks.BEFORE_ADD_LIQUIDITY_FLAG + & ~Hooks.AFTER_ADD_LIQUIDITY_FLAG & ~Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG & ~Hooks.BEFORE_SWAP_FLAG + & ~Hooks.AFTER_SWAP_FLAG & ~Hooks.BEFORE_DONATE_FLAG & ~Hooks.AFTER_DONATE_FLAG + ) + ) + ); + + deploy(skipCallsTestHook); + + 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, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 1); + } + function test_beforeSwap_skipIfCalledByHook() public { - IPoolManager.SwapParams memory swapParams = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address( + uint160(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF) + & uint160( + ~Hooks.BEFORE_INITIALIZE_FLAG & ~Hooks.AFTER_INITIALIZE_FLAG & ~Hooks.BEFORE_ADD_LIQUIDITY_FLAG + & ~Hooks.AFTER_ADD_LIQUIDITY_FLAG & ~Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG + & ~Hooks.AFTER_REMOVE_LIQUIDITY_FLAG & ~Hooks.AFTER_SWAP_FLAG & ~Hooks.BEFORE_DONATE_FLAG + & ~Hooks.AFTER_DONATE_FLAG + ) + ) + ); + + deploy(skipCallsTestHook); + + MockERC20(Currency.unwrap(key.currency0)).approve(address(skipCallsTestHook), Constants.MAX_UINT256); + + assertEq(skipCallsTestHook.counter(), 0); + swapRouter.swap(key, swapParams, testSettings, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 1); + } + + function test_afterSwap_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address( + uint160(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF) + & uint160( + ~Hooks.BEFORE_INITIALIZE_FLAG & ~Hooks.AFTER_INITIALIZE_FLAG & ~Hooks.BEFORE_ADD_LIQUIDITY_FLAG + & ~Hooks.AFTER_ADD_LIQUIDITY_FLAG & ~Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG + & ~Hooks.AFTER_REMOVE_LIQUIDITY_FLAG & ~Hooks.BEFORE_SWAP_FLAG & ~Hooks.BEFORE_DONATE_FLAG + & ~Hooks.AFTER_DONATE_FLAG + ) + ) + ); - PoolSwapTest.TestSettings memory testSettings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true, currencyAlreadySent: false}); + deploy(skipCallsTestHook); MockERC20(Currency.unwrap(key.currency0)).approve(address(skipCallsTestHook), Constants.MAX_UINT256); @@ -49,4 +193,48 @@ contract SkipCallsTest is Test, Deployers, GasSnapshot { swapRouter.swap(key, swapParams, testSettings, abi.encode(address(this))); assertEq(skipCallsTestHook.counter(), 1); } + + function test_beforeDonate_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address( + uint160(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF) + & uint160( + ~Hooks.BEFORE_INITIALIZE_FLAG & ~Hooks.AFTER_INITIALIZE_FLAG & ~Hooks.BEFORE_ADD_LIQUIDITY_FLAG + & ~Hooks.AFTER_ADD_LIQUIDITY_FLAG & ~Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG + & ~Hooks.AFTER_REMOVE_LIQUIDITY_FLAG & ~Hooks.BEFORE_SWAP_FLAG & ~Hooks.AFTER_SWAP_FLAG + & ~Hooks.AFTER_DONATE_FLAG + ) + ) + ); + + deploy(skipCallsTestHook); + + assertEq(skipCallsTestHook.counter(), 0); + MockERC20(Currency.unwrap(key.currency0)).approve(address(skipCallsTestHook), Constants.MAX_UINT256); + MockERC20(Currency.unwrap(key.currency1)).approve(address(skipCallsTestHook), Constants.MAX_UINT256); + donateRouter.donate(key, 100, 200, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 1); + } + + function test_afterDonate_skipIfCalledByHook() public { + SkipCallsTestHook skipCallsTestHook = SkipCallsTestHook( + address( + uint160(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF) + & uint160( + ~Hooks.BEFORE_INITIALIZE_FLAG & ~Hooks.AFTER_INITIALIZE_FLAG & ~Hooks.BEFORE_ADD_LIQUIDITY_FLAG + & ~Hooks.AFTER_ADD_LIQUIDITY_FLAG & ~Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG + & ~Hooks.AFTER_REMOVE_LIQUIDITY_FLAG & ~Hooks.BEFORE_SWAP_FLAG & ~Hooks.AFTER_SWAP_FLAG + & ~Hooks.BEFORE_DONATE_FLAG + ) + ) + ); + + deploy(skipCallsTestHook); + + assertEq(skipCallsTestHook.counter(), 0); + MockERC20(Currency.unwrap(key.currency0)).approve(address(skipCallsTestHook), Constants.MAX_UINT256); + MockERC20(Currency.unwrap(key.currency1)).approve(address(skipCallsTestHook), Constants.MAX_UINT256); + donateRouter.donate(key, 100, 200, abi.encode(address(this))); + assertEq(skipCallsTestHook.counter(), 1); + } }