diff --git a/src/base/ImmutableState.sol b/src/base/ImmutableState.sol index 708a3d281..4b35794f3 100644 --- a/src/base/ImmutableState.sol +++ b/src/base/ImmutableState.sol @@ -10,6 +10,15 @@ contract ImmutableState is IImmutableState { /// @inheritdoc IImmutableState IPoolManager public immutable poolManager; + /// @notice Thrown when the caller is not PoolManager + error NotPoolManager(); + + /// @notice Only allow calls from the PoolManager contract + modifier onlyPoolManager() { + if (msg.sender != address(poolManager)) revert NotPoolManager(); + _; + } + constructor(IPoolManager _poolManager) { poolManager = _poolManager; } diff --git a/src/base/SafeCallback.sol b/src/base/SafeCallback.sol index 45e1c6bf9..b4f993093 100644 --- a/src/base/SafeCallback.sol +++ b/src/base/SafeCallback.sol @@ -8,17 +8,8 @@ import {ImmutableState} from "./ImmutableState.sol"; /// @title Safe Callback /// @notice A contract that only allows the Uniswap v4 PoolManager to call the unlockCallback abstract contract SafeCallback is ImmutableState, IUnlockCallback { - /// @notice Thrown when calling unlockCallback where the caller is not PoolManager - error NotPoolManager(); - constructor(IPoolManager _poolManager) ImmutableState(_poolManager) {} - /// @notice Only allow calls from the PoolManager contract - modifier onlyPoolManager() { - if (msg.sender != address(poolManager)) revert NotPoolManager(); - _; - } - /// @inheritdoc IUnlockCallback /// @dev We force the onlyPoolManager modifier by exposing a virtual function after the onlyPoolManager check. function unlockCallback(bytes calldata data) external onlyPoolManager returns (bytes memory) { diff --git a/src/base/hooks/BaseHook.sol b/src/base/hooks/BaseHook.sol deleted file mode 100644 index 635602a63..000000000 --- a/src/base/hooks/BaseHook.sol +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {BeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; -import {SafeCallback} from "../SafeCallback.sol"; - -/// @title Base Hook -/// @notice abstract contract for hook implementations -abstract contract BaseHook is IHooks, SafeCallback { - error NotSelf(); - error InvalidPool(); - error LockFailure(); - error HookNotImplemented(); - - constructor(IPoolManager _manager) SafeCallback(_manager) { - validateHookAddress(this); - } - - /// @dev Only this address may call this function - modifier selfOnly() { - if (msg.sender != address(this)) revert NotSelf(); - _; - } - - /// @dev Only pools with hooks set to this contract may call this function - modifier onlyValidPools(IHooks hooks) { - if (hooks != this) revert InvalidPool(); - _; - } - - /// @notice Returns a struct of permissions to signal which hook functions are to be implemented - /// @dev Used at deployment to validate the address correctly represents the expected permissions - function getHookPermissions() public pure virtual returns (Hooks.Permissions memory); - - /// @notice Validates the deployed hook address agrees with the expected permissions of the hook - /// @dev this function is virtual so that we can override it during testing, - /// which allows us to deploy an implementation to any address - /// and then etch the bytecode into the correct address - function validateHookAddress(BaseHook _this) internal pure virtual { - Hooks.validateHookPermissions(_this, getHookPermissions()); - } - - function _unlockCallback(bytes calldata data) internal virtual override returns (bytes memory) { - (bool success, bytes memory returnData) = address(this).call(data); - if (success) return returnData; - if (returnData.length == 0) revert LockFailure(); - // if the call failed, bubble up the reason - assembly ("memory-safe") { - revert(add(returnData, 32), mload(returnData)) - } - } - - /// @inheritdoc IHooks - function beforeInitialize(address, PoolKey calldata, uint160) external virtual returns (bytes4) { - revert HookNotImplemented(); - } - - /// @inheritdoc IHooks - function afterInitialize(address, PoolKey calldata, uint160, int24) external virtual returns (bytes4) { - revert HookNotImplemented(); - } - - /// @inheritdoc IHooks - function beforeAddLiquidity(address, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata) - external - virtual - returns (bytes4) - { - revert HookNotImplemented(); - } - - /// @inheritdoc IHooks - function beforeRemoveLiquidity( - address, - PoolKey calldata, - IPoolManager.ModifyLiquidityParams calldata, - bytes calldata - ) external virtual returns (bytes4) { - revert HookNotImplemented(); - } - - /// @inheritdoc IHooks - function afterAddLiquidity( - address, - PoolKey calldata, - IPoolManager.ModifyLiquidityParams calldata, - BalanceDelta, - BalanceDelta, - bytes calldata - ) external virtual returns (bytes4, BalanceDelta) { - revert HookNotImplemented(); - } - - /// @inheritdoc IHooks - function afterRemoveLiquidity( - address, - PoolKey calldata, - IPoolManager.ModifyLiquidityParams calldata, - BalanceDelta, - BalanceDelta, - bytes calldata - ) external virtual returns (bytes4, BalanceDelta) { - revert HookNotImplemented(); - } - - /// @inheritdoc IHooks - function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) - external - virtual - returns (bytes4, BeforeSwapDelta, uint24) - { - revert HookNotImplemented(); - } - - /// @inheritdoc IHooks - function afterSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) - external - virtual - returns (bytes4, int128) - { - revert HookNotImplemented(); - } - - /// @inheritdoc IHooks - function beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) - external - virtual - returns (bytes4) - { - revert HookNotImplemented(); - } - - /// @inheritdoc IHooks - function afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) - external - virtual - returns (bytes4) - { - revert HookNotImplemented(); - } -} diff --git a/src/utils/BaseHook.sol b/src/utils/BaseHook.sol new file mode 100644 index 000000000..236ec26d5 --- /dev/null +++ b/src/utils/BaseHook.sol @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {BeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {ImmutableState} from "../base/ImmutableState.sol"; + +/// @title Base Hook +/// @notice abstract contract for hook implementations +abstract contract BaseHook is IHooks, ImmutableState { + error NotSelf(); + error InvalidPool(); + error HookNotImplemented(); + + constructor(IPoolManager _manager) ImmutableState(_manager) { + validateHookAddress(this); + } + + /// @notice Returns a struct of permissions to signal which hook functions are to be implemented + /// @dev Used at deployment to validate the address correctly represents the expected permissions + function getHookPermissions() public pure virtual returns (Hooks.Permissions memory); + + /// @notice Validates the deployed hook address agrees with the expected permissions of the hook + /// @dev this function is virtual so that we can override it during testing, + /// which allows us to deploy an implementation to any address + /// and then etch the bytecode into the correct address + function validateHookAddress(BaseHook _this) internal pure virtual { + Hooks.validateHookPermissions(_this, getHookPermissions()); + } + + /// @inheritdoc IHooks + function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) + external + onlyPoolManager + returns (bytes4) + { + return _beforeInitialize(sender, key, sqrtPriceX96); + } + + function _beforeInitialize(address, PoolKey calldata, uint160) internal virtual returns (bytes4) { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick) + external + onlyPoolManager + returns (bytes4) + { + return _afterInitialize(sender, key, sqrtPriceX96, tick); + } + + function _afterInitialize(address, PoolKey calldata, uint160, int24) internal virtual returns (bytes4) { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function beforeAddLiquidity( + address sender, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external onlyPoolManager returns (bytes4) { + return _beforeAddLiquidity(sender, key, params, hookData); + } + + function _beforeAddLiquidity(address, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata) + internal + virtual + returns (bytes4) + { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function beforeRemoveLiquidity( + address sender, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external onlyPoolManager returns (bytes4) { + return _beforeRemoveLiquidity(sender, key, params, hookData); + } + + function _beforeRemoveLiquidity( + address, + PoolKey calldata, + IPoolManager.ModifyLiquidityParams calldata, + bytes calldata + ) internal virtual returns (bytes4) { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function afterAddLiquidity( + address sender, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata params, + BalanceDelta delta, + BalanceDelta feesAccrued, + bytes calldata hookData + ) external onlyPoolManager returns (bytes4, BalanceDelta) { + return _afterAddLiquidity(sender, key, params, delta, feesAccrued, hookData); + } + + function _afterAddLiquidity( + address, + PoolKey calldata, + IPoolManager.ModifyLiquidityParams calldata, + BalanceDelta, + BalanceDelta, + bytes calldata + ) internal virtual returns (bytes4, BalanceDelta) { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function afterRemoveLiquidity( + address sender, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata params, + BalanceDelta delta, + BalanceDelta feesAccrued, + bytes calldata hookData + ) external onlyPoolManager returns (bytes4, BalanceDelta) { + return _afterRemoveLiquidity(sender, key, params, delta, feesAccrued, hookData); + } + + function _afterRemoveLiquidity( + address, + PoolKey calldata, + IPoolManager.ModifyLiquidityParams calldata, + BalanceDelta, + BalanceDelta, + bytes calldata + ) internal virtual returns (bytes4, BalanceDelta) { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function beforeSwap( + address sender, + PoolKey calldata key, + IPoolManager.SwapParams calldata params, + bytes calldata hookData + ) external onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) { + return _beforeSwap(sender, key, params, hookData); + } + + function _beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) + internal + virtual + returns (bytes4, BeforeSwapDelta, uint24) + { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function afterSwap( + address sender, + PoolKey calldata key, + IPoolManager.SwapParams calldata params, + BalanceDelta delta, + bytes calldata hookData + ) external onlyPoolManager returns (bytes4, int128) { + return _afterSwap(sender, key, params, delta, hookData); + } + + function _afterSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) + internal + virtual + returns (bytes4, int128) + { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function beforeDonate( + address sender, + PoolKey calldata key, + uint256 amount0, + uint256 amount1, + bytes calldata hookData + ) external onlyPoolManager returns (bytes4) { + return _beforeDonate(sender, key, amount0, amount1, hookData); + } + + function _beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + internal + virtual + returns (bytes4) + { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function afterDonate( + address sender, + PoolKey calldata key, + uint256 amount0, + uint256 amount1, + bytes calldata hookData + ) external onlyPoolManager returns (bytes4) { + return _afterDonate(sender, key, amount0, amount1, hookData); + } + + function _afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + internal + virtual + returns (bytes4) + { + revert HookNotImplemented(); + } +} diff --git a/test/SafeCallback.t.sol b/test/SafeCallback.t.sol index 1e7673227..301bd6b75 100644 --- a/test/SafeCallback.t.sol +++ b/test/SafeCallback.t.sol @@ -5,6 +5,7 @@ import "forge-std/Test.sol"; import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; import {SafeCallback} from "../src/base/SafeCallback.sol"; +import {ImmutableState} from "../src/base/ImmutableState.sol"; import {MockSafeCallback} from "./mocks/MockSafeCallback.sol"; contract SafeCallbackTest is Test, Deployers { @@ -26,7 +27,7 @@ contract SafeCallbackTest is Test, Deployers { function test_unlockRevert(address caller, bytes calldata data) public { vm.startPrank(caller); - if (caller != address(manager)) vm.expectRevert(SafeCallback.NotPoolManager.selector); + if (caller != address(manager)) vm.expectRevert(ImmutableState.NotPoolManager.selector); safeCallback.unlockCallback(data); vm.stopPrank(); }