-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathVolumeBasedFeeHook.sol
More file actions
348 lines (310 loc) · 12.6 KB
/
Copy pathVolumeBasedFeeHook.sol
File metadata and controls
348 lines (310 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
pragma solidity ^0.8.24;
import {BaseHook} from "v4-periphery/src/utils/BaseHook.sol";
import {Hooks} from "v4-core/src/libraries/Hooks.sol";
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol";
import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "v4-core/src/types/BeforeSwapDelta.sol";
import {CustomRevert} from "v4-core/src/libraries/CustomRevert.sol";
import {SwapParams} from "v4-core/src/types/PoolOperation.sol";
import {LPFeeLibrary} from "v4-core/src/libraries/LPFeeLibrary.sol";
/**
* @dev Volume-based dynamic fee hook that adjusts swap fees based on transaction volume.
* The hook implements a tiered fee structure where fees decrease as transaction volume increases.
*
* The fee calculation works differently for each token (currency0 and currency1):
* - Below minAmount: Uses defaultFee
* - Between minAmount and maxAmount: Linear interpolation between feeAtMinAmount and feeAtMaxAmount
* - Above maxAmount: Uses feeAtMaxAmount
*
* Fee relationships must maintain:
* - feeAtMinAmount <= defaultFee
* - feeAtMaxAmount <= feeAtMinAmount
* - minAmount < maxAmount
*
* NOTE: The fee calculation is symmetric for both swap directions and both exact input/output swaps.
*/
contract VolumeBasedFeeHook is BaseHook {
using PoolIdLibrary for PoolKey;
using CustomRevert for bytes4;
/// @dev Struct of parameters for the hook
struct SwapVolumeParams {
uint24 defaultFee;
uint24 feeAtMinAmount0;
uint24 feeAtMaxAmount0;
uint24 feeAtMinAmount1;
uint24 feeAtMaxAmount1;
uint256 minAmount0;
uint256 maxAmount0;
uint256 minAmount1;
uint256 maxAmount1;
}
/**
* @dev Thrown when fee relationships are invalid:
* - feeAtMinAmount is greater than defaultFee
* - feeAtMaxAmount is greater than feeAtMinAmount
*/
error InvalidFees();
/**
* @dev Thrown when amount thresholds are invalid:
* - minAmount is greater than or equal to maxAmount
*/
error InvalidAmountThresholds();
/// @dev The default fee applied when swap amount is below minAmount
uint24 immutable defaultFee;
/// @dev Fee applied at minAmount0 threshold for currency0
uint24 immutable feeAtMinAmount0;
/// @dev Fee applied at maxAmount0 threshold for currency0
uint24 immutable feeAtMaxAmount0;
/// @dev Fee applied at minAmount1 threshold for currency1
uint24 immutable feeAtMinAmount1;
/// @dev Fee applied at maxAmount1 threshold for currency1
uint24 immutable feeAtMaxAmount1;
/// @dev Difference between feeAtMinAmount and feeAtMaxAmount for currency0
uint24 immutable feeAtThresholdsDelta0;
/// @dev Difference between feeAtMinAmount and feeAtMaxAmount for currency1
uint24 immutable feeAtThresholdsDelta1;
/// @dev Minimum amount threshold for currency0
uint256 immutable minAmount0;
/// @dev Maximum amount threshold for currency0
uint256 immutable maxAmount0;
/// @dev Minimum amount threshold for currency1
uint256 immutable minAmount1;
/// @dev Maximum amount threshold for currency1
uint256 immutable maxAmount1;
/// @dev Difference between minAmount and maxAmount for currency0
uint256 immutable amountThresholdsDelta0;
/// @dev Difference between minAmount and maxAmount for currency1
uint256 immutable amountThresholdsDelta1;
/**
* @dev Constructor that validates and sets the fee parameters.
* Reverts if:
* - Any fee at min amount is higher than the default fee
* - Any fee at max amount is higher than or equal to its corresponding fee at min fee
* - Any min amount is greater than or equal to its max amount
*/
constructor(
IPoolManager _poolManager,
SwapVolumeParams memory params
) BaseHook(_poolManager) {
if (params.feeAtMinAmount0 > params.defaultFee)
InvalidFees.selector.revertWith();
if (params.feeAtMaxAmount0 > params.feeAtMinAmount0)
InvalidFees.selector.revertWith();
if (params.feeAtMinAmount1 > params.defaultFee)
InvalidFees.selector.revertWith();
if (params.feeAtMaxAmount1 > params.feeAtMinAmount1)
InvalidFees.selector.revertWith();
if (params.minAmount0 >= params.maxAmount0)
InvalidAmountThresholds.selector.revertWith();
if (params.minAmount1 >= params.maxAmount1)
InvalidAmountThresholds.selector.revertWith();
defaultFee = params.defaultFee;
feeAtMinAmount0 = params.feeAtMinAmount0;
feeAtMaxAmount0 = params.feeAtMaxAmount0;
feeAtMinAmount1 = params.feeAtMinAmount1;
feeAtMaxAmount1 = params.feeAtMaxAmount1;
/**
* Difference between fee at min amount and fee at max amount for currency0
* Used for linear interpolation
*/
feeAtThresholdsDelta0 = params.feeAtMinAmount0 - params.feeAtMaxAmount0;
feeAtThresholdsDelta1 = params.feeAtMinAmount1 - params.feeAtMaxAmount1;
minAmount0 = params.minAmount0;
maxAmount0 = params.maxAmount0;
minAmount1 = params.minAmount1;
maxAmount1 = params.maxAmount1;
/**
* Difference between min amount and max amount for currency0
* Used for linear interpolation
*/
amountThresholdsDelta0 = params.maxAmount0 - params.minAmount0;
amountThresholdsDelta1 = params.maxAmount1 - params.minAmount1;
}
/**
* @dev Handles the beforeSwap hook by calculating and updating the dynamic LP fee based on
* the swap amount.
* @return The hook selector, zero delta (no hooks modifying swap amounts), and zero fee
*/
function _beforeSwap(
address,
PoolKey calldata,
SwapParams calldata swapParams,
bytes calldata
) internal virtual override returns (bytes4, BeforeSwapDelta, uint24) {
return (
BaseHook.beforeSwap.selector,
BeforeSwapDeltaLibrary.ZERO_DELTA,
_calculateFee(swapParams) | LPFeeLibrary.OVERRIDE_FEE_FLAG
);
}
/**
* @dev Calculates the appropriate fee based on swap parameters and direction.
* Handles both exact input and exact output swaps in both directions.
* @param swapParams The parameters of the swap including amount and direction
* @return calculatedFee The calculated fee as a uint24
*/
function _calculateFee(
SwapParams calldata swapParams
) internal view virtual returns (uint24 calculatedFee) {
/// Swapping currency0 for currency1
if (swapParams.zeroForOne) {
/**
* If amountSpecified is negative, it's an exact input swap
* Therefore, the known volume is the amount of currency0 being swapped
*/
if (swapParams.amountSpecified < 0) {
calculatedFee = _calculateFeePerScenario(
uint256(-swapParams.amountSpecified),
minAmount0,
maxAmount0,
feeAtMaxAmount0,
feeAtMinAmount0,
feeAtThresholdsDelta0,
amountThresholdsDelta0
);
} else {
/**
* If amountSpecified is positive, it's an exact output swap
* Therefore, the known volume is the amount of currency1 being swapped
*/
calculatedFee = _calculateFeePerScenario(
uint256(swapParams.amountSpecified),
minAmount1,
maxAmount1,
feeAtMaxAmount1,
feeAtMinAmount1,
feeAtThresholdsDelta1,
amountThresholdsDelta1
);
}
/// Swapping currency1 for currency0
} else {
/**
* If amountSpecified is negative, it's an exact input swap
* Therefore, the known volume is the amount of currency1 being swapped
*/
if (swapParams.amountSpecified < 0) {
calculatedFee = _calculateFeePerScenario(
uint256(-swapParams.amountSpecified),
minAmount1,
maxAmount1,
feeAtMaxAmount1,
feeAtMinAmount1,
feeAtThresholdsDelta1,
amountThresholdsDelta1
);
} else {
/**
* If amountSpecified is positive, it's an exact output swap
* Therefore, the known volume is the amount of currency0 being swapped
*/
calculatedFee = _calculateFeePerScenario(
uint256(swapParams.amountSpecified),
minAmount0,
maxAmount0,
feeAtMaxAmount0,
feeAtMinAmount0,
feeAtThresholdsDelta0,
amountThresholdsDelta0
);
}
}
}
/**
* @dev Calculates the fee for a specific volume based on the defined thresholds.
* Implements linear interpolation between min and max amounts.
* @param volume The transaction volume to calculate fee for
* @param minAmount The minimum amount threshold
* @param maxAmount The maximum amount threshold
* @param feeAtMaxAmount The fee at maximum amount
* @param feeAtMinAmount The fee at minimum amount
* @return calculatedFee The calculated fee as a uint24
*/
function _calculateFeePerScenario(
uint256 volume,
uint256 minAmount,
uint256 maxAmount,
uint24 feeAtMaxAmount,
uint24 feeAtMinAmount,
uint24 feeAtThresholdsDelta,
uint256 amountThresholdsDelta
) internal view virtual returns (uint24) {
/**
* Explicitly check if volume is equal to minAmount
* To allow benefitting of fee reduction
* Saving gas on skipping the linear interpolation
*/
if (volume == minAmount) {
return feeAtMinAmount;
}
if (volume >= maxAmount) {
return feeAtMaxAmount;
}
if (volume < minAmount) {
return defaultFee;
}
/**
* Volume is between minAmount and maxAmount
* Calculate the fee using linear interpolation
*/
return
feeAtMinAmount -
uint24(
(feeAtThresholdsDelta * (volume - minAmount)) /
amountThresholdsDelta
);
}
/**
* @dev Returns the current fee parameters of the hook.
* @return SwapVolumeParams struct containing all fee and amount threshold parameters
*/
function getSwapVolumeParams()
external
view
virtual
returns (SwapVolumeParams memory)
{
return
SwapVolumeParams({
defaultFee: defaultFee,
feeAtMinAmount0: feeAtMinAmount0,
feeAtMaxAmount0: feeAtMaxAmount0,
feeAtMinAmount1: feeAtMinAmount1,
feeAtMaxAmount1: feeAtMaxAmount1,
minAmount0: minAmount0,
maxAmount0: maxAmount0,
minAmount1: minAmount1,
maxAmount1: maxAmount1
});
}
/**
* @dev Sets the hook permissions. Only requires beforeSwap permission to update dynamic fees.
* @return permissions The hook permissions
*/
function getHookPermissions()
public
pure
virtual
override
returns (Hooks.Permissions memory)
{
return
Hooks.Permissions({
beforeInitialize: false,
afterInitialize: false,
beforeAddLiquidity: false,
afterAddLiquidity: false,
beforeRemoveLiquidity: false,
afterRemoveLiquidity: false,
beforeSwap: true,
afterSwap: false,
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: false,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}
}