Skip to content

Commit

Permalink
Cache dynamic fee in slot0 (#360)
Browse files Browse the repository at this point in the history
  • Loading branch information
snreynolds authored Nov 1, 2023
1 parent 9b91232 commit 4e77c83
Show file tree
Hide file tree
Showing 26 changed files with 239 additions and 97 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
85384
1 change: 1 addition & 0 deletions .forge-snapshots/cached dynamic fee, no hooks.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
82539
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 @@
96005
95983
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 @@
153358
153336
2 changes: 1 addition & 1 deletion .forge-snapshots/initialize.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
38009
38126
1 change: 0 additions & 1 deletion .forge-snapshots/poolExtsloadSlot0.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/poolExtsloadTickInfoStruct.snap

This file was deleted.

2 changes: 1 addition & 1 deletion .forge-snapshots/simple swap.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
49720
49771
Original file line number Diff line number Diff line change
@@ -1 +1 @@
124982
125033
2 changes: 1 addition & 1 deletion .forge-snapshots/swap against liquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
109692
109743
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 @@
91502
84493
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with hooks.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
49691
49742
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
49720
49771
1 change: 1 addition & 0 deletions .forge-snapshots/update dynamic fee in before swap.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
91231
9 changes: 9 additions & 0 deletions contracts/Fees.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ import {FeeLibrary} from "./libraries/FeeLibrary.sol";
import {Pool} from "./libraries/Pool.sol";
import {PoolKey} from "./types/PoolKey.sol";
import {Owned} from "./Owned.sol";
import {IDynamicFeeManager} from "./interfaces/IDynamicFeeManager.sol";

abstract contract Fees is IFees, Owned {
using FeeLibrary for uint24;
using CurrencyLibrary for Currency;

uint8 public constant MIN_PROTOCOL_FEE_DENOMINATOR = 4;

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

mapping(Currency currency => uint256) public protocolFeesAccrued;

mapping(address hookAddress => mapping(Currency currency => uint256)) public hookFeesAccrued;
Expand Down Expand Up @@ -61,6 +65,11 @@ abstract contract Fees is IFees, Owned {
}
}

function _fetchDynamicSwapFee(PoolKey memory key) internal view returns (uint24 dynamicSwapFee) {
dynamicSwapFee = IDynamicFeeManager(address(key.hooks)).getFee(msg.sender, key);
if (dynamicSwapFee >= MAX_SWAP_FEE) revert FeeTooLarge();
}

/// @dev Only the lower 12 bits are used here to encode the fee denominator.
function _checkProtocolFee(uint16 fee) internal pure {
if (fee != 0) {
Expand Down
43 changes: 20 additions & 23 deletions contracts/PoolManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,10 @@ contract PoolManager is IPoolManager, Fees, NoDelegateCall, ERC1155, IERC1155Rec
}

PoolId id = key.toId();
uint24 protocolFees = _fetchProtocolFees(key);
uint24 hookFees = _fetchHookFees(key);
tick = pools[id].initialize(sqrtPriceX96, protocolFees, hookFees);

uint24 swapFee = key.fee.isDynamicFee() ? _fetchDynamicSwapFee(key) : key.fee.getStaticFee();

tick = pools[id].initialize(sqrtPriceX96, _fetchProtocolFees(key), _fetchHookFees(key), swapFee);

if (key.hooks.shouldCallAfterInitialize()) {
if (
Expand All @@ -133,6 +134,7 @@ contract PoolManager is IPoolManager, Fees, NoDelegateCall, ERC1155, IERC1155Rec
}
}

// On intitalize we emit the key's fee, which tells us all fee settings a pool can have: either a static swap fee or dynamic swap fee and if the hook has enabled swap or withdraw fees.
emit Initialize(id, key.currency0, key.currency1, key.fee, key.tickSpacing, key.hooks);
}

Expand Down Expand Up @@ -251,23 +253,14 @@ contract PoolManager is IPoolManager, Fees, NoDelegateCall, ERC1155, IERC1155Rec
}
}

// Set the total swap fee, either through the hook or as the static fee set an initialization.
uint24 totalSwapFee;
if (key.fee.isDynamicFee()) {
totalSwapFee = IDynamicFeeManager(address(key.hooks)).getFee(msg.sender, key, params, hookData);
if (totalSwapFee >= 1000000) revert FeeTooLarge();
} else {
// clear the top 4 bits since they may be flagged for hook fees
totalSwapFee = key.fee.getStaticFee();
}
PoolId id = key.toId();

uint256 feeForProtocol;
uint256 feeForHook;
uint24 swapFee;
Pool.SwapState memory state;
PoolId id = key.toId();
(delta, feeForProtocol, feeForHook, state) = pools[id].swap(
(delta, feeForProtocol, feeForHook, swapFee, state) = pools[id].swap(
Pool.SwapParams({
fee: totalSwapFee,
tickSpacing: key.tickSpacing,
zeroForOne: params.zeroForOne,
amountSpecified: params.amountSpecified,
Expand All @@ -294,14 +287,7 @@ contract PoolManager is IPoolManager, Fees, NoDelegateCall, ERC1155, IERC1155Rec
}

emit Swap(
id,
msg.sender,
delta.amount0(),
delta.amount1(),
state.sqrtPriceX96,
state.liquidity,
state.tick,
totalSwapFee
id, msg.sender, delta.amount0(), delta.amount1(), state.sqrtPriceX96, state.liquidity, state.tick, swapFee
);
}

Expand Down Expand Up @@ -388,6 +374,17 @@ contract PoolManager is IPoolManager, Fees, NoDelegateCall, ERC1155, IERC1155Rec
emit HookFeeUpdated(id, newHookFees);
}

function updateDynamicSwapFee(PoolKey memory key) external {
if (key.fee.isDynamicFee()) {
uint24 newDynamicSwapFee = _fetchDynamicSwapFee(key);
PoolId id = key.toId();
pools[id].setSwapFee(newDynamicSwapFee);
emit DynamicSwapFeeUpdated(id, newDynamicSwapFee);
} else {
revert FeeNotDynamic();
}
}

function extsload(bytes32 slot) external view returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
Expand Down
4 changes: 1 addition & 3 deletions contracts/interfaces/IDynamicFeeManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,5 @@ import {IPoolManager} from "./IPoolManager.sol";
/// @notice The dynamic fee manager determines fees for pools
/// @dev note that this pool is only called if the PoolKey fee value is equal to the DYNAMIC_FEE magic value
interface IDynamicFeeManager {
function getFee(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata data)
external
returns (uint24);
function getFee(address sender, PoolKey calldata key) external view returns (uint24);
}
2 changes: 2 additions & 0 deletions contracts/interfaces/IFees.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ interface IFees {
error FeeTooLarge();
/// @notice Thrown when not enough gas is provided to look up the protocol fee
error ProtocolFeeCannotBeFetched();
/// @notice Thrown when a pool does not have a dynamic fee.
error FeeNotDynamic();

event ProtocolFeeControllerUpdated(address protocolFeeController);

Expand Down
5 changes: 5 additions & 0 deletions contracts/interfaces/IPoolManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ interface IPoolManager is IFees, IERC1155 {

event HookFeeUpdated(PoolId indexed id, uint24 hookFees);

event DynamicSwapFeeUpdated(PoolId indexed id, uint24 dynamicSwapFee);

/// @notice Returns the constant representing the maximum tickSpacing for an initialized pool key
function MAX_TICK_SPACING() external view returns (int24);

Expand Down Expand Up @@ -187,6 +189,9 @@ interface IPoolManager is IFees, IERC1155 {
/// @notice Sets the hook's swap and withdrawal fees for the given pool
function setHookFees(PoolKey memory key) external;

/// @notice Updates the pools swap fees for the a pool that has enabled dynamic swap fees.
function updateDynamicSwapFee(PoolKey memory key) external;

/// @notice Called by external contracts to access granular pool state
/// @param slot Key of slot to sload
/// @return value The value of the slot as bytes32
Expand Down
31 changes: 25 additions & 6 deletions contracts/libraries/Pool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ library Pool {
int24 tick;
uint24 protocolFees;
uint24 hookFees;
// used for the swap fee, either static at initialize or dynamic via hook
uint24 swapFee;
}
// 24 bits left!

// info stored for each initialized individual tick
struct TickInfo {
Expand Down Expand Up @@ -107,15 +108,21 @@ library Pool {
if (tickUpper > TickMath.MAX_TICK) revert TickUpperOutOfBounds(tickUpper);
}

function initialize(State storage self, uint160 sqrtPriceX96, uint24 protocolFees, uint24 hookFees)
function initialize(State storage self, uint160 sqrtPriceX96, uint24 protocolFees, uint24 hookFees, uint24 swapFee)
internal
returns (int24 tick)
{
if (self.slot0.sqrtPriceX96 != 0) revert PoolAlreadyInitialized();

tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96);

self.slot0 = Slot0({sqrtPriceX96: sqrtPriceX96, tick: tick, protocolFees: protocolFees, hookFees: hookFees});
self.slot0 = Slot0({
sqrtPriceX96: sqrtPriceX96,
tick: tick,
protocolFees: protocolFees,
hookFees: hookFees,
swapFee: swapFee
});
}

function getSwapFee(uint24 feesStorage) internal pure returns (uint16) {
Expand All @@ -138,6 +145,12 @@ library Pool {
self.slot0.hookFees = hookFees;
}

/// @notice Only dynamic fee pools may update the swap fee.
function setSwapFee(State storage self, uint24 swapFee) internal {
if (self.slot0.sqrtPriceX96 == 0) revert PoolNotInitialized();
self.slot0.swapFee = swapFee;
}

struct ModifyPositionParams {
// the address that owns the position
address owner;
Expand Down Expand Up @@ -367,7 +380,6 @@ library Pool {
}

struct SwapParams {
uint24 fee;
int24 tickSpacing;
bool zeroForOne;
int256 amountSpecified;
Expand All @@ -377,11 +389,18 @@ library Pool {
/// @dev Executes a swap against the state, and returns the amount deltas of the pool
function swap(State storage self, SwapParams memory params)
internal
returns (BalanceDelta result, uint256 feeForProtocol, uint256 feeForHook, SwapState memory state)
returns (
BalanceDelta result,
uint256 feeForProtocol,
uint256 feeForHook,
uint24 swapFee,
SwapState memory state
)
{
if (params.amountSpecified == 0) revert SwapAmountCannotBeZero();

Slot0 memory slot0Start = self.slot0;
swapFee = slot0Start.swapFee;
if (slot0Start.sqrtPriceX96 == 0) revert PoolNotInitialized();
if (params.zeroForOne) {
if (params.sqrtPriceLimitX96 >= slot0Start.sqrtPriceX96) {
Expand Down Expand Up @@ -446,7 +465,7 @@ library Pool {
) ? params.sqrtPriceLimitX96 : step.sqrtPriceNextX96,
state.liquidity,
state.amountSpecifiedRemaining,
params.fee
swapFee
);

if (exactInput) {
Expand Down
47 changes: 47 additions & 0 deletions contracts/test/DynamicFeesTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {BaseTestHooks} from "./BaseTestHooks.sol";
import {IDynamicFeeManager} from "../interfaces/IDynamicFeeManager.sol";
import {PoolKey} from "../types/PoolKey.sol";
import {IPoolManager} from "../interfaces/IPoolManager.sol";
import {IHooks} from "../interfaces/IHooks.sol";

contract DynamicFeesTest is BaseTestHooks, IDynamicFeeManager {
uint24 internal fee;
IPoolManager manager;

constructor() {}

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

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

function getFee(address, PoolKey calldata) public view returns (uint24) {
return fee;
}

function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata hookData)
external
override
returns (bytes4)
{
// updates the dynamic fee in the pool if update is true
bool _update;
uint24 _fee;

if (hookData.length > 0) {
(_update, _fee) = abi.decode(hookData, (bool, uint24));
}
if (_update == true) {
fee = _fee;

manager.updateDynamicSwapFee(key);
}
return IHooks.beforeSwap.selector;
}
}
Loading

0 comments on commit 4e77c83

Please sign in to comment.