Skip to content

Commit

Permalink
merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
hensha256 committed May 17, 2024
2 parents 4a226a7 + 975bb80 commit 6a38859
Show file tree
Hide file tree
Showing 40 changed files with 373 additions and 83 deletions.
2 changes: 1 addition & 1 deletion .forge-snapshots/addLiquidity CA fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
331831
331641
2 changes: 1 addition & 1 deletion .forge-snapshots/addLiquidity with empty hook.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
286606
286634
2 changes: 1 addition & 1 deletion .forge-snapshots/poolManager bytecode size.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
22097
22118
2 changes: 1 addition & 1 deletion .forge-snapshots/removeLiquidity CA fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
187273
187083
2 changes: 1 addition & 1 deletion .forge-snapshots/simple swap with native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
117304
117431
2 changes: 1 addition & 1 deletion .forge-snapshots/simple swap.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
132465
132592
2 changes: 1 addition & 1 deletion .forge-snapshots/swap CA custom curve + swap noop.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
134799
134711
2 changes: 1 addition & 1 deletion .forge-snapshots/swap CA fee on unspecified.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
183793
183730
Original file line number Diff line number Diff line change
@@ -1 +1 @@
112929
113056
2 changes: 1 addition & 1 deletion .forge-snapshots/swap against liquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
124272
124399
2 changes: 1 addition & 1 deletion .forge-snapshots/swap burn 6909 for input.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
136325
136452
2 changes: 1 addition & 1 deletion .forge-snapshots/swap burn native 6909 for input.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
125453
125580
2 changes: 1 addition & 1 deletion .forge-snapshots/swap mint native output as 6909.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
147514
147641
2 changes: 1 addition & 1 deletion .forge-snapshots/swap mint output as 6909.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
164319
164446
Original file line number Diff line number Diff line change
@@ -1 +1 @@
223093
223312
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with dynamic fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
148520
148647
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with hooks.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
124284
124411
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with lp fee and protocol fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
180775
180884
1 change: 1 addition & 0 deletions .forge-snapshots/swap with return dynamic fee.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
156513
2 changes: 1 addition & 1 deletion .forge-snapshots/update dynamic fee in before swap.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
159006
159121
32 changes: 19 additions & 13 deletions src/PoolManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -227,19 +227,25 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim
PoolId id = key.toId();
_checkPoolInitialized(id);

(int256 amountToSwap, BeforeSwapDelta beforeSwapDelta) = key.hooks.beforeSwap(key, params, hookData);

// execute swap, account protocol fees, and emit swap event
swapDelta = _swap(
id,
Pool.SwapParams({
tickSpacing: key.tickSpacing,
zeroForOne: params.zeroForOne,
amountSpecified: amountToSwap,
sqrtPriceLimitX96: params.sqrtPriceLimitX96
}),
params.zeroForOne ? key.currency0 : key.currency1 // input token
);
BeforeSwapDelta beforeSwapDelta;
{
int256 amountToSwap;
uint24 lpFeeOverride;
(amountToSwap, beforeSwapDelta, lpFeeOverride) = key.hooks.beforeSwap(key, params, hookData);

// execute swap, account protocol fees, and emit swap event
swapDelta = _swap(
id,
Pool.SwapParams({
tickSpacing: key.tickSpacing,
zeroForOne: params.zeroForOne,
amountSpecified: amountToSwap,
sqrtPriceLimitX96: params.sqrtPriceLimitX96,
lpFeeOverride: lpFeeOverride
}),
params.zeroForOne ? key.currency0 : key.currency1 // input token
);
}

BalanceDelta hookDelta;
(swapDelta, hookDelta) = key.hooks.afterSwap(key, params, swapDelta, hookData, beforeSwapDelta);
Expand Down
4 changes: 2 additions & 2 deletions src/ProtocolFees.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ abstract contract ProtocolFees is IProtocolFees, Owned {
/// @inheritdoc IProtocolFees
function setProtocolFee(PoolKey memory key, uint24 newProtocolFee) external {
if (msg.sender != address(protocolFeeController)) revert InvalidCaller();
if (!newProtocolFee.validate()) revert InvalidProtocolFee();
if (!newProtocolFee.isValidProtocolFee()) revert InvalidProtocolFee();
PoolId id = key.toId();
_getPool(id).setProtocolFee(newProtocolFee);
emit ProtocolFeeUpdated(id, newProtocolFee);
Expand Down Expand Up @@ -77,7 +77,7 @@ abstract contract ProtocolFees is IProtocolFees, Owned {
returnData := mload(add(_data, 0x20))
}
// Ensure return data does not overflow a uint24 and that the underlying fees are within bounds.
(success, protocolFees) = (returnData == uint24(returnData)) && uint24(returnData).validate()
(success, protocolFees) = (returnData == uint24(returnData)) && uint24(returnData).isValidProtocolFee()
? (true, uint24(returnData))
: (false, 0);
}
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/IHooks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,13 @@ interface IHooks {
/// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BeforeSwapDelta The hook's delta in specified and unspecified currencies. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
/// @return uint24 Optionally override the lp fee, only used if three conditions are met: 1) the Pool has a dynamic fee, 2) the value's leading bit is set to 1 (24th bit, 0x800000), 3) the value is less than or equal to the maximum fee (1 million)
function beforeSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata hookData
) external returns (bytes4, BeforeSwapDelta);
) external returns (bytes4, BeforeSwapDelta, uint24);

/// @notice The hook called after a swap
/// @param sender The initial msg.sender for the swap call
Expand Down
34 changes: 15 additions & 19 deletions src/libraries/Hooks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {LPFeeLibrary} from "./LPFeeLibrary.sol";
import {BalanceDelta, toBalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol";
import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../types/BeforeSwapDelta.sol";
import {IPoolManager} from "../interfaces/IPoolManager.sol";
import {ParseBytes} from "../libraries/ParseBytes.sol";

/// @notice V4 decides whether to invoke specific hooks by inspecting the leading bits of the address that
/// the hooks contract is deployed to.
Expand All @@ -18,6 +19,7 @@ library Hooks {
using Hooks for IHooks;
using SafeCast for int256;
using BeforeSwapDeltaLibrary for BeforeSwapDelta;
using ParseBytes for bytes;

uint256 internal constant BEFORE_INITIALIZE_FLAG = 1 << 159;
uint256 internal constant AFTER_INITIALIZE_FLAG = 1 << 158;
Expand Down Expand Up @@ -125,14 +127,8 @@ library Hooks {
(success, result) = address(self).call(data);
if (!success) _revert(result);

bytes4 expectedSelector;
bytes4 selector;
assembly {
expectedSelector := mload(add(data, 0x20))
selector := mload(add(result, 0x20))
}

if (selector != expectedSelector) revert InvalidHookResponse();
// Check expected selector and returned selector match.
if (result.parseSelector() != data.parseSelector()) revert InvalidHookResponse();
}

/// @notice performs a hook call using the given calldata on the given hook
Expand All @@ -145,7 +141,7 @@ library Hooks {

// If this hook wasnt meant to return something, default to 0 delta
if (!parseReturn) return 0;
(, delta) = abi.decode(result, (bytes4, int256));
return result.parseReturnDelta();
}

/// @notice modifier to prevent calling a hook if they initiated the action
Expand Down Expand Up @@ -236,22 +232,22 @@ 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, BeforeSwapDelta hookReturn)
returns (int256 amountToSwap, BeforeSwapDelta hookReturn, uint24 lpFeeOverride)
{
amountToSwap = params.amountSpecified;
if (msg.sender == address(self)) return (amountToSwap, BeforeSwapDeltaLibrary.ZERO_DELTA);
if (msg.sender == address(self)) return (amountToSwap, BeforeSwapDeltaLibrary.ZERO_DELTA, lpFeeOverride);

if (self.hasPermission(BEFORE_SWAP_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
)
);
bytes memory result =
callHook(self, abi.encodeWithSelector(IHooks.beforeSwap.selector, msg.sender, key, params, hookData));

// dynamic fee pools that do not want to override the cache fee, return 0 otherwise they return a valid fee with the override flag
if (key.fee.isDynamicFee()) lpFeeOverride = result.parseFee();

// skip this logic for the case where the hook return is 0
if (canReturnDelta) {
if (self.hasPermission(BEFORE_SWAP_RETURNS_DELTA_FLAG)) {
hookReturn = BeforeSwapDelta.wrap(result.parseReturnDelta());

// any return in unspecified is passed to the afterSwap hook for handling
int128 hookDeltaSpecified = hookReturn.getSpecifiedDelta();

Expand Down
31 changes: 29 additions & 2 deletions src/libraries/LPFeeLibrary.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,29 @@ library LPFeeLibrary {
/// @notice Thrown when the static or dynamic fee on a pool exceeds 100%.
error FeeTooLarge();

uint24 public constant STATIC_FEE_MASK = 0x7FFFFF;
uint24 public constant FEE_MASK = 0x7FFFFF;
uint24 public constant OVERRIDE_MASK = 0xBFFFFF;

// the top bit of the fee in a PoolKey is used to signal if a Pool's LP fee is dynamic
uint24 public constant DYNAMIC_FEE_FLAG = 0x800000;

// the second bit of the fee returned by beforeSwap is used to signal if the stored LP fee should be overridden in this swap
// only dynamic-fee pools can return a fee via the beforeSwap hook
uint24 public constant OVERRIDE_FEE_FLAG = 0x400000;

// the lp fee is represented in hundredths of a bip, so the max is 100%
uint24 public constant MAX_LP_FEE = 1000000;

function isDynamicFee(uint24 self) internal pure returns (bool) {
return self & DYNAMIC_FEE_FLAG != 0;
}

function isValid(uint24 self) internal pure returns (bool) {
return self <= MAX_LP_FEE;
}

function validate(uint24 self) internal pure {
if (self > MAX_LP_FEE) revert FeeTooLarge();
if (!self.isValid()) revert FeeTooLarge();
}

function getInitialLPFee(uint24 self) internal pure returns (uint24) {
Expand All @@ -29,4 +40,20 @@ library LPFeeLibrary {
self.validate();
return self;
}

/// @notice returns true if the fee has the override flag set (top bit of the uint24)
function isOverride(uint24 self) internal pure returns (bool) {
return self & OVERRIDE_FEE_FLAG != 0;
}

/// @notice returns a fee with the override flag removed
function removeOverrideFlag(uint24 self) internal pure returns (uint24) {
return self & OVERRIDE_MASK;
}

/// @notice Removes the override flag and validates the fee (reverts if the fee is too large)
function removeOverrideAndValidate(uint24 self) internal pure returns (uint24 fee) {
fee = self.removeOverrideFlag();
fee.validate();
}
}
29 changes: 29 additions & 0 deletions src/libraries/ParseBytes.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.8.20;

/// @notice Parses bytes returned from hooks and the byte selector used to check return selectors from hooks.
/// @dev parseSelector also is used to parse the expected selector
/// For parsing hook returns, note that all hooks return either bytes4 or (bytes4, 32-byte-delta) or (bytes4, 32-byte-delta, uint24).
library ParseBytes {
function parseSelector(bytes memory result) internal pure returns (bytes4 selector) {
// equivalent: (selector,) = abi.decode(result, (bytes4, int256));
assembly {
selector := mload(add(result, 0x20))
}
}

function parseFee(bytes memory result) internal pure returns (uint24 lpFee) {
// equivalent: (,, lpFee) = abi.decode(result, (bytes4, int256, uint24));
assembly {
lpFee := mload(add(result, 0x60))
}
}

function parseReturnDelta(bytes memory result) internal pure returns (int256 hookReturn) {
// equivalent: (, hookReturnDelta) = abi.decode(result, (bytes4, int256));
assembly {
hookReturn := mload(add(result, 0x40))
}
}
}
9 changes: 7 additions & 2 deletions src/libraries/Pool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ library Pool {
using Position for Position.Info;
using Pool for State;
using ProtocolFeeLibrary for uint24;
using LPFeeLibrary for uint24;

/// @notice Thrown when tickLower is not below tickUpper
/// @param tickLower The invalid tickLower
Expand Down Expand Up @@ -294,6 +295,7 @@ library Pool {
bool zeroForOne;
int256 amountSpecified;
uint160 sqrtPriceLimitX96;
uint24 lpFeeOverride;
}

/// @notice Executes a swap against the state, and returns the amount deltas of the pool
Expand All @@ -317,8 +319,11 @@ library Pool {
state.feeGrowthGlobalX128 = zeroForOne ? self.feeGrowthGlobal0X128 : self.feeGrowthGlobal1X128;
state.liquidity = cache.liquidityStart;

swapFee =
cache.protocolFee == 0 ? slot0Start.lpFee : uint24(cache.protocolFee).calculateSwapFee(slot0Start.lpFee);
// if the beforeSwap hook returned a valid fee override, use that as the LP fee, otherwise load from storage
uint24 lpFee =
params.lpFeeOverride.isOverride() ? params.lpFeeOverride.removeOverrideAndValidate() : slot0Start.lpFee;

swapFee = cache.protocolFee == 0 ? lpFee : uint24(cache.protocolFee).calculateSwapFee(lpFee);

bool exactInput = params.amountSpecified < 0;

Expand Down
2 changes: 1 addition & 1 deletion src/libraries/ProtocolFeeLibrary.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ library ProtocolFeeLibrary {
return uint16(self >> 12);
}

function validate(uint24 self) internal pure returns (bool) {
function isValidProtocolFee(uint24 self) internal pure returns (bool) {
if (self != 0) {
uint16 fee0 = getZeroForOneFee(self);
uint16 fee1 = getOneForZeroFee(self);
Expand Down
2 changes: 1 addition & 1 deletion src/test/BaseTestHooks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ contract BaseTestHooks is IHooks {
PoolKey calldata, /* key **/
IPoolManager.SwapParams calldata, /* params **/
bytes calldata /* hookData **/
) external virtual returns (bytes4, BeforeSwapDelta) {
) external virtual returns (bytes4, BeforeSwapDelta, uint24) {
revert HookNotImplemented();
}

Expand Down
4 changes: 2 additions & 2 deletions src/test/CustomCurveHook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ contract CustomCurveHook is BaseTestHooks {
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata /* hookData **/
) external override onlyPoolManager returns (bytes4, BeforeSwapDelta) {
) external override onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) {
(Currency inputCurrency, Currency outputCurrency, uint256 amount) = _getInputOutputAndAmount(key, params);

// this "custom curve" is a line, 1-1
Expand All @@ -47,7 +47,7 @@ contract CustomCurveHook is BaseTestHooks {

// return -amountSpecified as specified to no-op the concentrated liquidity swap
BeforeSwapDelta hookDelta = toBeforeSwapDelta(int128(-params.amountSpecified), int128(params.amountSpecified));
return (IHooks.beforeSwap.selector, hookDelta);
return (IHooks.beforeSwap.selector, hookDelta, 0);
}

function afterAddLiquidity(
Expand Down
Loading

0 comments on commit 6a38859

Please sign in to comment.