diff --git a/.forge-snapshots/addLiquidity CA fee.snap b/.forge-snapshots/addLiquidity CA fee.snap index 01aac461d..71d1ed3ad 100644 --- a/.forge-snapshots/addLiquidity CA fee.snap +++ b/.forge-snapshots/addLiquidity CA fee.snap @@ -1 +1 @@ -329513 \ No newline at end of file +329498 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index c21766680..2fbc5708e 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -284148 \ No newline at end of file +284133 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index 3ac62f913..900bc98f5 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -23596 \ No newline at end of file +23597 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity CA fee.snap b/.forge-snapshots/removeLiquidity CA fee.snap index 9127a3985..5f9b1f75e 100644 --- a/.forge-snapshots/removeLiquidity CA fee.snap +++ b/.forge-snapshots/removeLiquidity CA fee.snap @@ -1 +1 @@ -185086 \ No newline at end of file +185071 \ No newline at end of file diff --git a/.forge-snapshots/swap CA custom curve + swap noop.snap b/.forge-snapshots/swap CA custom curve + swap noop.snap index 3fcb2ed06..992bd7d4d 100644 --- a/.forge-snapshots/swap CA custom curve + swap noop.snap +++ b/.forge-snapshots/swap CA custom curve + swap noop.snap @@ -1 +1 @@ -135847 \ No newline at end of file +135875 \ No newline at end of file diff --git a/.forge-snapshots/swap CA fee on unspecified.snap b/.forge-snapshots/swap CA fee on unspecified.snap index 55c81bac6..e0ef3b93b 100644 --- a/.forge-snapshots/swap CA fee on unspecified.snap +++ b/.forge-snapshots/swap CA fee on unspecified.snap @@ -1 +1 @@ -184979 \ No newline at end of file +185033 \ No newline at end of file diff --git a/.forge-snapshots/swap skips hook call if hook is caller.snap b/.forge-snapshots/swap skips hook call if hook is caller.snap index 3d4300610..eb2ae5d84 100644 --- a/.forge-snapshots/swap skips hook call if hook is caller.snap +++ b/.forge-snapshots/swap skips hook call if hook is caller.snap @@ -1 +1 @@ -225044 \ No newline at end of file +225009 \ No newline at end of file diff --git a/.forge-snapshots/swap with lp fee and protocol fee.snap b/.forge-snapshots/swap with lp fee and protocol fee.snap index 203bd725e..f245f11ff 100644 --- a/.forge-snapshots/swap with lp fee and protocol fee.snap +++ b/.forge-snapshots/swap with lp fee and protocol fee.snap @@ -1 +1 @@ -181954 \ No newline at end of file +181919 \ 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 a9d97f7af..e204d6137 100644 --- a/.forge-snapshots/update dynamic fee in before swap.snap +++ b/.forge-snapshots/update dynamic fee in before swap.snap @@ -1 +1 @@ -160245 \ No newline at end of file +160210 \ No newline at end of file diff --git a/src/PoolManager.sol b/src/PoolManager.sol index 4239da5fa..d70dce273 100644 --- a/src/PoolManager.sol +++ b/src/PoolManager.sol @@ -17,7 +17,7 @@ import {ProtocolFees} from "./ProtocolFees.sol"; import {ERC6909Claims} from "./ERC6909Claims.sol"; import {PoolId, PoolIdLibrary} from "./types/PoolId.sol"; import {BalanceDelta, BalanceDeltaLibrary, toBalanceDelta} from "./types/BalanceDelta.sol"; -import {HookReturnDelta} from "./types/HookReturnDelta.sol"; +import {BeforeSwapDelta} from "./types/BeforeSwapDelta.sol"; import {Lock} from "./libraries/Lock.sol"; import {CurrencyDelta} from "./libraries/CurrencyDelta.sol"; import {NonZeroDeltaCount} from "./libraries/NonZeroDeltaCount.sol"; @@ -271,7 +271,7 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim PoolId id = key.toId(); _checkPoolInitialized(id); - (int256 amountToSwap, HookReturnDelta hookReturnDelta) = key.hooks.beforeSwap(key, params, hookData); + (int256 amountToSwap, BeforeSwapDelta beforeSwapDelta) = key.hooks.beforeSwap(key, params, hookData); // execute swap, account protocol fees, and emit swap event swapDelta = _swap( @@ -286,7 +286,7 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim ); BalanceDelta hookDelta; - (swapDelta, hookDelta) = key.hooks.afterSwap(key, params, swapDelta, hookData, hookReturnDelta); + (swapDelta, hookDelta) = key.hooks.afterSwap(key, params, swapDelta, hookData, beforeSwapDelta); // if the hook doesnt have the flag to be able to return deltas, hookDelta will always be 0 if (hookDelta != BalanceDeltaLibrary.ZERO_DELTA) _accountPoolBalanceDelta(key, hookDelta, address(key.hooks)); diff --git a/src/interfaces/IHooks.sol b/src/interfaces/IHooks.sol index 539cb7263..b111251c2 100644 --- a/src/interfaces/IHooks.sol +++ b/src/interfaces/IHooks.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.24; import {PoolKey} from "../types/PoolKey.sol"; import {BalanceDelta} from "../types/BalanceDelta.sol"; import {IPoolManager} from "./IPoolManager.sol"; -import {BeforeSwapDelta} from "../types/HookReturnDelta.sol"; +import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; /// @notice The PoolManager contract decides whether to invoke specific hooks by inspecting the leading bits /// of the hooks contract address. For example, a 1 bit in the first bit of the address will diff --git a/src/libraries/Hooks.sol b/src/libraries/Hooks.sol index cb0e36d1e..5c6d9dff2 100644 --- a/src/libraries/Hooks.sol +++ b/src/libraries/Hooks.sol @@ -6,7 +6,7 @@ import {IHooks} from "../interfaces/IHooks.sol"; import {SafeCast} from "../libraries/SafeCast.sol"; import {LPFeeLibrary} from "./LPFeeLibrary.sol"; import {BalanceDelta, toBalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol"; -import {HookReturnDelta, HookReturnDeltaLibrary} from "../types/HookReturnDelta.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../types/BeforeSwapDelta.sol"; import {IPoolManager} from "../interfaces/IPoolManager.sol"; /// @notice V4 decides whether to invoke specific hooks by inspecting the leading bits of the address that @@ -17,7 +17,7 @@ library Hooks { using LPFeeLibrary for uint24; using Hooks for IHooks; using SafeCast for int256; - using HookReturnDeltaLibrary for HookReturnDelta; + using BeforeSwapDeltaLibrary for BeforeSwapDelta; uint256 internal constant BEFORE_INITIALIZE_FLAG = 1 << 159; uint256 internal constant AFTER_INITIALIZE_FLAG = 1 << 158; @@ -139,12 +139,13 @@ library Hooks { /// @return delta The delta returned by the hook function callHookWithReturnDelta(IHooks self, bytes memory data, bool parseReturn) internal - returns (HookReturnDelta delta) + returns (int256 delta) { bytes memory result = callHook(self, data); - if (!parseReturn) return HookReturnDeltaLibrary.ZERO_DELTA; - (, delta) = abi.decode(result, (bytes4, HookReturnDelta)); + // If this hook wasnt meant to return something, default to 0 delta + if (!parseReturn) return 0; + (, delta) = abi.decode(result, (bytes4, int256)); } /// @notice modifier to prevent calling a hook if they initiated the action @@ -207,20 +208,26 @@ library Hooks { callerDelta = delta; if (params.liquidityDelta > 0) { if (self.hasPermission(AFTER_ADD_LIQUIDITY_FLAG)) { - hookDelta = self.callHookWithReturnDelta( - abi.encodeWithSelector(IHooks.afterAddLiquidity.selector, msg.sender, key, params, delta, hookData), - self.hasPermission(AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG) - ).toBalanceDelta(); + hookDelta = BalanceDelta.wrap( + self.callHookWithReturnDelta( + abi.encodeWithSelector( + IHooks.afterAddLiquidity.selector, msg.sender, key, params, delta, hookData + ), + self.hasPermission(AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG) + ) + ); callerDelta = callerDelta - hookDelta; } } else { if (self.hasPermission(AFTER_REMOVE_LIQUIDITY_FLAG)) { - hookDelta = self.callHookWithReturnDelta( - abi.encodeWithSelector( - IHooks.afterRemoveLiquidity.selector, msg.sender, key, params, delta, hookData - ), - self.hasPermission(AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG) - ).toBalanceDelta(); + hookDelta = BalanceDelta.wrap( + self.callHookWithReturnDelta( + abi.encodeWithSelector( + IHooks.afterRemoveLiquidity.selector, msg.sender, key, params, delta, hookData + ), + self.hasPermission(AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG) + ) + ); callerDelta = callerDelta - hookDelta; } } @@ -229,24 +236,31 @@ library Hooks { /// @notice calls beforeSwap hook if permissioned and validates return value function beforeSwap(IHooks self, PoolKey memory key, IPoolManager.SwapParams memory params, bytes calldata hookData) internal - returns (int256 amountToSwap, HookReturnDelta hookReturn) + returns (int256 amountToSwap, BeforeSwapDelta hookReturn) { amountToSwap = params.amountSpecified; - if (msg.sender == address(self)) return (amountToSwap, HookReturnDeltaLibrary.ZERO_DELTA); + if (msg.sender == address(self)) return (amountToSwap, BeforeSwapDeltaLibrary.ZERO_DELTA); if (self.hasPermission(BEFORE_SWAP_FLAG)) { - hookReturn = self.callHookWithReturnDelta( - abi.encodeWithSelector(IHooks.beforeSwap.selector, msg.sender, key, params, hookData), - self.hasPermission(BEFORE_SWAP_RETURNS_DELTA_FLAG) + bool canReturnDelta = self.hasPermission(BEFORE_SWAP_RETURNS_DELTA_FLAG); + hookReturn = BeforeSwapDelta.wrap( + self.callHookWithReturnDelta( + abi.encodeWithSelector(IHooks.beforeSwap.selector, msg.sender, key, params, hookData), + canReturnDelta + ) ); - // any return in unspecified is passed to the afterSwap hook for handling - int128 hookDeltaSpecified = hookReturn.getSpecifiedDelta(); - - // Update the swap amount according to the hook's return, and check that the swap type doesnt change (exact input/output) - if (hookDeltaSpecified != 0) { - bool exactInput = amountToSwap < 0; - amountToSwap += hookDeltaSpecified; - if (exactInput ? amountToSwap > 0 : amountToSwap < 0) revert HookDeltaExceedsSwapAmount(); + + // skip this logic for the case where the hook return is 0 + if (canReturnDelta) { + // any return in unspecified is passed to the afterSwap hook for handling + int128 hookDeltaSpecified = hookReturn.getSpecifiedDelta(); + + // Update the swap amount according to the hook's return, and check that the swap type doesnt change (exact input/output) + if (hookDeltaSpecified != 0) { + bool exactInput = amountToSwap < 0; + amountToSwap += hookDeltaSpecified; + if (exactInput ? amountToSwap > 0 : amountToSwap < 0) revert HookDeltaExceedsSwapAmount(); + } } } } @@ -258,7 +272,7 @@ library Hooks { IPoolManager.SwapParams memory params, BalanceDelta swapDelta, bytes calldata hookData, - HookReturnDelta beforeSwapHookReturn + BeforeSwapDelta beforeSwapHookReturn ) internal returns (BalanceDelta, BalanceDelta) { if (msg.sender == address(self)) return (swapDelta, BalanceDeltaLibrary.ZERO_DELTA); @@ -269,7 +283,7 @@ library Hooks { hookDeltaUnspecified += self.callHookWithReturnDelta( abi.encodeWithSelector(IHooks.afterSwap.selector, msg.sender, key, params, swapDelta, hookData), self.hasPermission(AFTER_SWAP_RETURNS_DELTA_FLAG) - ).getUnspecifiedDelta(); + ).toInt128(); } BalanceDelta hookDelta; diff --git a/src/test/BaseTestHooks.sol b/src/test/BaseTestHooks.sol index 042f4b7a6..061887dc2 100644 --- a/src/test/BaseTestHooks.sol +++ b/src/test/BaseTestHooks.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.24; import {IHooks} from "../interfaces/IHooks.sol"; import {PoolKey} from "../types/PoolKey.sol"; import {BalanceDelta} from "../types/BalanceDelta.sol"; -import {BeforeSwapDelta} from "../types/HookReturnDelta.sol"; +import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; import {IPoolManager} from "../interfaces/IPoolManager.sol"; contract BaseTestHooks is IHooks { diff --git a/src/test/CustomCurveHook.sol b/src/test/CustomCurveHook.sol index 0f22a6dca..1135e380d 100644 --- a/src/test/CustomCurveHook.sol +++ b/src/test/CustomCurveHook.sol @@ -6,7 +6,7 @@ import {SafeCast} from "../libraries/SafeCast.sol"; import {IHooks} from "../interfaces/IHooks.sol"; import {IPoolManager} from "../interfaces/IPoolManager.sol"; import {PoolKey} from "../types/PoolKey.sol"; -import {BeforeSwapDelta, toBeforeSwapDelta} from "../types/HookReturnDelta.sol"; +import {BeforeSwapDelta, toBeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; import {BalanceDelta} from "../types/BalanceDelta.sol"; import {Currency} from "../types/Currency.sol"; import {CurrencySettleTake} from "../libraries/CurrencySettleTake.sol"; diff --git a/src/test/DeltaReturningHook.sol b/src/test/DeltaReturningHook.sol index 1fbdc55ef..d8b7e2d9e 100644 --- a/src/test/DeltaReturningHook.sol +++ b/src/test/DeltaReturningHook.sol @@ -12,7 +12,7 @@ import {Currency} from "../types/Currency.sol"; import {BaseTestHooks} from "./BaseTestHooks.sol"; import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol"; import {CurrencyLibrary, Currency} from "../types/Currency.sol"; -import {BeforeSwapDelta, toBeforeSwapDelta} from "../types/HookReturnDelta.sol"; +import {BeforeSwapDelta, toBeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; contract DeltaReturningHook is BaseTestHooks { using Hooks for IHooks; diff --git a/src/test/DynamicFeesTestHook.sol b/src/test/DynamicFeesTestHook.sol index bbb0d3ae7..7111ea784 100644 --- a/src/test/DynamicFeesTestHook.sol +++ b/src/test/DynamicFeesTestHook.sol @@ -5,7 +5,7 @@ import {BaseTestHooks} from "./BaseTestHooks.sol"; import {PoolKey} from "../types/PoolKey.sol"; import {IPoolManager} from "../interfaces/IPoolManager.sol"; import {IHooks} from "../interfaces/IHooks.sol"; -import {BeforeSwapDelta} from "../types/HookReturnDelta.sol"; +import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; contract DynamicFeesTestHook is BaseTestHooks { uint24 internal fee; diff --git a/src/test/EmptyTestHooks.sol b/src/test/EmptyTestHooks.sol index 9ae048134..abdb08eb3 100644 --- a/src/test/EmptyTestHooks.sol +++ b/src/test/EmptyTestHooks.sol @@ -6,7 +6,7 @@ import {IHooks} from "../interfaces/IHooks.sol"; import {IPoolManager} from "../interfaces/IPoolManager.sol"; import {PoolKey} from "../types/PoolKey.sol"; import {BalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol"; -import {BeforeSwapDelta} from "../types/HookReturnDelta.sol"; +import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; contract EmptyTestHooks is IHooks { using Hooks for IHooks; diff --git a/src/test/MockHooks.sol b/src/test/MockHooks.sol index e8cc12397..f3a5f0c31 100644 --- a/src/test/MockHooks.sol +++ b/src/test/MockHooks.sol @@ -7,7 +7,7 @@ import {IPoolManager} from "../interfaces/IPoolManager.sol"; import {PoolKey} from "../types/PoolKey.sol"; import {BalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol"; import {PoolId, PoolIdLibrary} from "../types/PoolId.sol"; -import {BeforeSwapDelta} from "../types/HookReturnDelta.sol"; +import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; contract MockHooks is IHooks { using PoolIdLibrary for PoolKey; diff --git a/src/test/SkipCallsTestHook.sol b/src/test/SkipCallsTestHook.sol index a32911f79..b664b2add 100644 --- a/src/test/SkipCallsTestHook.sol +++ b/src/test/SkipCallsTestHook.sol @@ -14,7 +14,7 @@ import {PoolTestBase} from "./PoolTestBase.sol"; import {Constants} from "../../test/utils/Constants.sol"; import {Test} from "forge-std/Test.sol"; import {CurrencySettleTake} from "../libraries/CurrencySettleTake.sol"; -import {BeforeSwapDelta} from "../types/HookReturnDelta.sol"; +import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; contract SkipCallsTestHook is BaseTestHooks, Test { using CurrencySettleTake for Currency; diff --git a/src/types/HookReturnDelta.sol b/src/types/BeforeSwapDelta.sol similarity index 50% rename from src/types/HookReturnDelta.sol rename to src/types/BeforeSwapDelta.sol index cbea1ea8d..c0e24f002 100644 --- a/src/types/HookReturnDelta.sol +++ b/src/types/BeforeSwapDelta.sol @@ -3,12 +3,6 @@ pragma solidity ^0.8.20; import {BalanceDelta} from "./BalanceDelta.sol"; -// Type used only internally to decode the different types of deltas returned by hooks -// For afterAddLiquidity and afterRemoveLiquidity, HookReturnDelta is a BalanceDelta -// For beforeSwap, HookReturnDelta is a BeforeSwapDelta -// For afterSwap, HookReturnDelta is an int128 in the lower 128 bits -type HookReturnDelta is bytes32; - // Return type of the beforeSwap hook. // Upper 128 bits is the delta in specified tokens. Lower 128 bits is delta in unspecified tokens (to match the afterSwap hook) type BeforeSwapDelta is int256; @@ -24,28 +18,20 @@ function toBeforeSwapDelta(int128 deltaSpecified, int128 deltaUnspecified) } } -library HookReturnDeltaLibrary { - HookReturnDelta public constant ZERO_DELTA = HookReturnDelta.wrap(bytes32(0)); - - /// converts a HookReturnDelta type to a BalanceDelta type - /// returned by afterAddLiquidity and afterRemoveLiquidity - function toBalanceDelta(HookReturnDelta delta) internal pure returns (BalanceDelta balanceDelta) { - assembly { - balanceDelta := delta - } - } +library BeforeSwapDeltaLibrary { + BeforeSwapDelta public constant ZERO_DELTA = BeforeSwapDelta.wrap(0); - /// extracts int128 from the upper 128 bits of the HookReturnDelta + /// extracts int128 from the upper 128 bits of the BeforeSwapDelta /// returned by beforeSwap - function getSpecifiedDelta(HookReturnDelta delta) internal pure returns (int128 deltaSpecified) { + function getSpecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaSpecified) { assembly { deltaSpecified := shr(128, delta) } } - /// extracts int128 from the lower 128 bits of the HookReturnDelta + /// extracts int128 from the lower 128 bits of the BeforeSwapDelta /// returned by beforeSwap and afterSwap - function getUnspecifiedDelta(HookReturnDelta delta) internal pure returns (int128 deltaUnspecified) { + function getUnspecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaUnspecified) { /// @solidity memory-safe-assembly assembly { deltaUnspecified := delta