Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

beforeSwap returns dynamic fee #648

Merged
merged 26 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
36e7653
allow beforeSwap to return and set the lpFee
saucepoint May 13, 2024
07a9d30
fix tests; default to a sentinel for Hooks.beforeSwap
saucepoint May 13, 2024
28311ce
merge in main; regenerate snapshots
saucepoint May 14, 2024
7406eb9
define constant
saucepoint May 14, 2024
761ccb9
very smol optimization
saucepoint May 14, 2024
f424c1b
Optimise before swap return fee (#650)
hensha256 May 14, 2024
ad6dcf6
Try shared parse lib (#652)
snreynolds May 14, 2024
cd95832
Update src/interfaces/IHooks.sol
saucepoint May 14, 2024
52ef386
misc
saucepoint May 14, 2024
132a877
merge in main; regenerate snapshots
saucepoint May 14, 2024
616bc84
merge in main; regenerate snapshots; fix test
saucepoint May 15, 2024
f508681
review comments; abstract fee checks; add new test
saucepoint May 15, 2024
b5929ae
make ProtocolFeeLibrary similar to LPFeeLibrary
saucepoint May 15, 2024
3a9747c
fix test
saucepoint May 15, 2024
160e4c6
rename how protocol fee is validated
saucepoint May 16, 2024
196af36
fee masking
saucepoint May 16, 2024
525108e
trial new override mechanism
hensha256 May 16, 2024
fdfa579
remove assembly block
hensha256 May 16, 2024
afcda81
cleanup; some natspec
saucepoint May 16, 2024
2ffaa92
revert if override fee exceeds the maximum
saucepoint May 16, 2024
60e1060
review comments; consolidate fee flag
saucepoint May 16, 2024
9499de9
use 2nd bit of uint24 to signal override
saucepoint May 16, 2024
bac221f
naming
saucepoint May 16, 2024
ef84cb0
Update src/libraries/LPFeeLibrary.sol
saucepoint May 17, 2024
35a1402
Update src/libraries/LPFeeLibrary.sol
saucepoint May 17, 2024
abb6988
additional code comment
saucepoint May 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1 +1 @@
151132
151126
2 changes: 1 addition & 1 deletion .forge-snapshots/addLiquidity CA fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
329498
329492
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 @@
284133
284127
2 changes: 1 addition & 1 deletion .forge-snapshots/addLiquidity with native token.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
141308
141302
2 changes: 1 addition & 1 deletion .forge-snapshots/addLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
151108
151102
Original file line number Diff line number Diff line change
@@ -1 +1 @@
299660
299654
2 changes: 1 addition & 1 deletion .forge-snapshots/donate gas with 1 token.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
108741
108735
2 changes: 1 addition & 1 deletion .forge-snapshots/donate gas with 2 tokens.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
149236
149230
2 changes: 1 addition & 1 deletion .forge-snapshots/initialize.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
62245
62239
2 changes: 1 addition & 1 deletion .forge-snapshots/poolManager bytecode size.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
23579
23804
2 changes: 1 addition & 1 deletion .forge-snapshots/removeLiquidity CA fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
185071
185065
2 changes: 1 addition & 1 deletion .forge-snapshots/removeLiquidity with empty hook.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
121089
121083
2 changes: 1 addition & 1 deletion .forge-snapshots/removeLiquidity with native token.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
117864
117858
2 changes: 1 addition & 1 deletion .forge-snapshots/removeLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
121077
121071
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 @@
117512
117648
2 changes: 1 addition & 1 deletion .forge-snapshots/simple swap.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
132708
132844
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 @@
135905
136267
saucepoint marked this conversation as resolved.
Show resolved Hide resolved
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 @@
185063
185199
Original file line number Diff line number Diff line change
@@ -1 +1 @@
114007
114143
2 changes: 1 addition & 1 deletion .forge-snapshots/swap against liquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
125385
125521
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 @@
137390
137525
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 @@
126490
126626
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 @@
148525
148660
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 @@
165420
165556
Original file line number Diff line number Diff line change
@@ -1 +1 @@
225039
225746
saucepoint marked this conversation as resolved.
Show resolved Hide resolved
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 @@
149687
149823
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with hooks.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
125397
125533
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 @@
181949
182500
saucepoint marked this conversation as resolved.
Show resolved Hide resolved
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 @@
157979
saucepoint marked this conversation as resolved.
Show resolved Hide resolved
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 @@
160240
160797
32 changes: 19 additions & 13 deletions src/PoolManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -271,19 +271,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;
{
hensha256 marked this conversation as resolved.
Show resolved Hide resolved
int256 amountToSwap;
uint24 fee;
(amountToSwap, beforeSwapDelta, fee) = 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,
fee: fee
}),
params.zeroForOne ? key.currency0 : key.currency1 // input token
);
}

BalanceDelta hookDelta;
(swapDelta, hookDelta) = key.hooks.afterSwap(key, params, swapDelta, hookData, beforeSwapDelta);
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 An optional swap fee, only used if the Pool has a dynamic fee and the value is less than of equal to LPFeeLibrary.MAX_LP_FEE
saucepoint marked this conversation as resolved.
Show resolved Hide resolved
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
36 changes: 29 additions & 7 deletions src/libraries/Hooks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,25 @@ library Hooks {
(, delta) = abi.decode(result, (bytes4, int256));
}

/// @notice performs a hook call using the given calldata on the given hook
/// @return delta The delta returned by the hook
/// @return fee The fee returned by the hook
function callHookWithReturnDeltaAndFee(IHooks self, bytes memory data, bool parseReturn, bool parseFee)
internal
returns (int256 delta, uint24 fee)
{
bytes memory result = callHook(self, data);
(, delta, fee) = abi.decode(result, (bytes4, int256, uint24));

if (!parseReturn) {
saucepoint marked this conversation as resolved.
Show resolved Hide resolved
delta = 0;
}

if (!parseFee) {
fee = type(uint24).max;
saucepoint marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// @notice modifier to prevent calling a hook if they initiated the action
modifier noSelfCall(IHooks self) {
if (msg.sender != address(self)) {
Expand Down Expand Up @@ -236,20 +255,23 @@ 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 lpFee)
{
amountToSwap = params.amountSpecified;
if (msg.sender == address(self)) return (amountToSwap, BeforeSwapDeltaLibrary.ZERO_DELTA);
lpFee = type(uint24).max;
if (msg.sender == address(self)) return (amountToSwap, BeforeSwapDeltaLibrary.ZERO_DELTA, type(uint24).max);
saucepoint marked this conversation as resolved.
Show resolved Hide resolved

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
)
int256 result;
(result, lpFee) = self.callHookWithReturnDeltaAndFee(
abi.encodeWithSelector(IHooks.beforeSwap.selector, msg.sender, key, params, hookData),
canReturnDelta,
key.fee.isDynamicFee()
);

hookReturn = BeforeSwapDelta.wrap(result);

// 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
Expand Down
5 changes: 3 additions & 2 deletions src/libraries/Pool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ library Pool {
bool zeroForOne;
int256 amountSpecified;
uint160 sqrtPriceLimitX96;
uint24 fee;
}

/// @notice Executes a swap against the state, and returns the amount deltas of the pool
Expand All @@ -319,8 +320,8 @@ 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);
uint24 _lpFee = params.fee <= LPFeeLibrary.MAX_LP_FEE ? params.fee : slot0Start.lpFee;
saucepoint marked this conversation as resolved.
Show resolved Hide resolved
swapFee = cache.protocolFee == 0 ? _lpFee : uint24(cache.protocolFee).calculateSwapFee(_lpFee);

if (!exactInput && (swapFee == LPFeeLibrary.MAX_LP_FEE)) {
revert InvalidFeeForExactOut();
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
4 changes: 2 additions & 2 deletions src/test/DeltaReturningHook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ contract DeltaReturningHook 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 specifiedCurrency, Currency unspecifiedCurrency) = _sortCurrencies(key, params);

if (deltaSpecified != 0) _settleOrTake(specifiedCurrency, deltaSpecified);
if (deltaUnspecifiedBeforeSwap != 0) _settleOrTake(unspecifiedCurrency, deltaUnspecifiedBeforeSwap);

BeforeSwapDelta beforeSwapDelta = toBeforeSwapDelta(deltaSpecified, deltaUnspecifiedBeforeSwap);

return (IHooks.beforeSwap.selector, beforeSwapDelta);
return (IHooks.beforeSwap.selector, beforeSwapDelta, 0);
}

function afterSwap(
Expand Down
4 changes: 2 additions & 2 deletions src/test/DynamicFeesTestHook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ contract DynamicFeesTestHook is BaseTestHooks {
function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata)
external
override
returns (bytes4, BeforeSwapDelta)
returns (bytes4, BeforeSwapDelta, uint24)
{
manager.updateDynamicLPFee(key, fee);
return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA);
return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, type(uint24).max);
}

function forcePoolFeeUpdate(PoolKey calldata _key, uint24 _fee) external {
Expand Down
29 changes: 29 additions & 0 deletions src/test/DynamicReturnFeeTestHook.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

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, BeforeSwapDeltaLibrary} from "../types/BeforeSwapDelta.sol";

contract DynamicReturnFeeTestHook is BaseTestHooks {
uint24 internal fee;
IPoolManager manager;

function setManager(IPoolManager _manager) external {
manager = _manager;
}

function setFee(uint24 _fee) external {
fee = _fee;
}

function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata)
external
override
returns (bytes4, BeforeSwapDelta, uint24)
{
return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, fee);
}
}
4 changes: 2 additions & 2 deletions src/test/EmptyTestHooks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ contract EmptyTestHooks is IHooks {
external
pure
override
returns (bytes4, BeforeSwapDelta)
returns (bytes4, BeforeSwapDelta, uint24)
{
return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA);
return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);
}

function afterSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata)
Expand Down
9 changes: 6 additions & 3 deletions src/test/MockHooks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,15 @@ contract MockHooks is IHooks {
function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata hookData)
external
override
returns (bytes4, BeforeSwapDelta)
returns (bytes4, BeforeSwapDelta, uint24)
{
beforeSwapData = hookData;
bytes4 selector = MockHooks.beforeSwap.selector;
return
(returnValues[selector] == bytes4(0) ? selector : returnValues[selector], BeforeSwapDeltaLibrary.ZERO_DELTA);
return (
returnValues[selector] == bytes4(0) ? selector : returnValues[selector],
BeforeSwapDeltaLibrary.ZERO_DELTA,
0
);
}

function afterSwap(
Expand Down
Loading
Loading