-
Notifications
You must be signed in to change notification settings - Fork 498
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
Middleware remove #145
base: base-middleware
Are you sure you want to change the base?
Middleware remove #145
Changes from 32 commits
62f093a
fcd3b34
bb2641c
266c61e
43717e8
8f41cf5
802f466
feb48f2
26524f4
f3cc913
c7353bb
33b1775
23a8644
3cb9689
a54a92e
087a262
f242024
4ecb379
9d1b36e
7602a8a
368e318
5e66d57
17885b9
a9cfd38
e5db8e2
1abe26d
675e85b
845aa81
b98912f
f1a680f
6f094c3
181b9ea
e13ba65
a266de8
800e169
d3ee037
6c4a6b6
511ce32
51f9aae
dcadfc7
ffde495
1c1d153
f3fc0ac
98e7fa5
d7c5302
1d1d91e
cb902e1
2a9026f
b973438
540664f
10f748e
2603731
de79c94
9c134d1
a96a8f6
f37734c
b065dcd
463d030
db359ca
0f03176
f38a66f
b54c7bc
e12bd98
eda7534
e77f698
c77742c
1f473ec
256f7a7
598b02e
2e1cf9b
c9cbfe2
fff8413
83ca829
a421a06
a44f768
1bbeef5
8011598
f8f6deb
92d6f70
8c91ab3
8d99c45
b230ee2
3eab6cb
e52fb04
fb02259
5e7e959
2b62627
564f941
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
311181 | ||
310035 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
122990 | ||
121849 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
80220 | ||
78755 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
1015181 | ||
1008752 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
110566 | ||
109419 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
240044 | ||
238654 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
45930 | ||
44465 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
79351 | ||
77891 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
122336 | ||
121999 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,5 +21,5 @@ jobs: | |
with: | ||
version: nightly | ||
|
||
- name: Run tests | ||
- name: Check format | ||
run: forge fmt --check |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.24; | ||
|
||
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; | ||
import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; | ||
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; | ||
import {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol"; | ||
import {BaseMiddleware} from "./BaseMiddleware.sol"; | ||
import {BaseHook} from "../BaseHook.sol"; | ||
import {BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; | ||
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; | ||
import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; | ||
|
||
abstract contract BaseRemove is BaseMiddleware { | ||
using TransientStateLibrary for IPoolManager; | ||
|
||
error HookPermissionForbidden(address hooks); | ||
error HookModifiedDeltas(); | ||
error FailedImplementationCall(); | ||
|
||
bytes internal constant ZERO_BYTES = bytes(""); | ||
uint256 public constant GAS_LIMIT = 5_000_000; | ||
uint256 public constant MAX_BIPS = 10_000; | ||
|
||
// use this hookdata to override checks to save gas. keccak256("override") - 1 | ||
bytes32 constant OVERRIDE_BYTES = 0x23b70c8dec38c3dec67a5596870027b04c4058cb3ac57b4e589bf628ac6669e7; | ||
|
||
uint256 public immutable maxFeeBips; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why declare this variable in this contract? seems like its never used here, and actually the implementing contract would set this value? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i was thinking it was better for standardization. if a third party calls a middleware and wants to read its maxFee, it would return zero. |
||
|
||
constructor(IPoolManager _manager, address _impl) BaseMiddleware(_manager, _impl) {} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add natspec!, this would be a good spot to document the hookData param as well |
||
function beforeRemoveLiquidity( | ||
address sender, | ||
PoolKey calldata key, | ||
IPoolManager.ModifyLiquidityParams calldata params, | ||
bytes calldata hookData | ||
) external returns (bytes4) { | ||
if (bytes32(hookData) == OVERRIDE_BYTES) { | ||
(bool success, bytes memory returnData) = address(implementation).delegatecall( | ||
abi.encodeWithSelector(this.beforeRemoveLiquidity.selector, sender, key, params, hookData[32:]) | ||
); | ||
return BaseHook.beforeRemoveLiquidity.selector; | ||
return abi.decode(returnData, (bytes4)); | ||
Jun1on marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
address(this).delegatecall{gas: GAS_LIMIT}( | ||
abi.encodeWithSelector(this._beforeRemoveLiquidity.selector, msg.data) | ||
); | ||
return BaseHook.beforeRemoveLiquidity.selector; | ||
Jun1on marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
function _beforeRemoveLiquidity(bytes calldata data) external { | ||
(bool success, bytes memory returnData) = address(implementation).delegatecall(data); | ||
if (!success) { | ||
revert FailedImplementationCall(); | ||
} | ||
(bytes4 selector) = abi.decode(returnData, (bytes4)); | ||
if (selector != BaseHook.beforeRemoveLiquidity.selector) { | ||
revert Hooks.InvalidHookResponse(); | ||
} | ||
if (manager.getNonzeroDeltaCount() != 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. technically nonzeroDeltaCount could be nonzero so long as its the same value that it was before the delegatecall to the impl There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you're right. while i initially thought of only the user case where someone removes liquidity through posm, someone could purpousely make nonzeroDeltaCount != 0 to circumvent the hook. |
||
revert HookModifiedDeltas(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.24; | ||
|
||
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; | ||
import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; | ||
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; | ||
import {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol"; | ||
import {BaseRemove} from "./BaseRemove.sol"; | ||
import {BaseHook} from "../BaseHook.sol"; | ||
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; | ||
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; | ||
import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; | ||
import {NonZeroDeltaCount} from "@uniswap/v4-core/src/libraries/NonZeroDeltaCount.sol"; | ||
import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; | ||
|
||
contract MiddlewareRemove is BaseRemove { | ||
using CustomRevert for bytes4; | ||
using Hooks for IHooks; | ||
using TransientStateLibrary for IPoolManager; | ||
|
||
error HookModifiedDeltasBeforeRemove(); | ||
error HookTookTooMuchFee(); | ||
error HookInvalidDeltasAfterRemove(); | ||
error MaxFeeBipsTooHigh(); | ||
|
||
constructor(IPoolManager _manager, address _impl, uint256 _maxFeeBips) BaseRemove(_manager, _impl) { | ||
if (_maxFeeBips > MAX_BIPS) revert MaxFeeBipsTooHigh(); | ||
maxFeeBips = _maxFeeBips; | ||
} | ||
|
||
function afterRemoveLiquidity( | ||
address sender, | ||
PoolKey calldata key, | ||
IPoolManager.ModifyLiquidityParams calldata params, | ||
BalanceDelta delta, | ||
bytes calldata hookData | ||
) external returns (bytes4, BalanceDelta) { | ||
if (bytes32(hookData) == OVERRIDE_BYTES) { | ||
(, bytes memory returnData) = address(implementation).delegatecall( | ||
abi.encodeWithSelector(this.afterRemoveLiquidity.selector, sender, key, params, delta, hookData[32:]) | ||
); | ||
return abi.decode(returnData, (bytes4, BalanceDelta)); | ||
} | ||
(bool success, bytes memory returnData) = address(this).delegatecall{gas: GAS_LIMIT}( | ||
Jun1on marked this conversation as resolved.
Show resolved
Hide resolved
|
||
abi.encodeWithSelector(this._callAndEnsureValidDeltas.selector, sender, key, params, delta, hookData) | ||
); | ||
if (success) { | ||
return (BaseHook.afterRemoveLiquidity.selector, abi.decode(returnData, (BalanceDelta))); | ||
} else { | ||
return (BaseHook.afterRemoveLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); | ||
} | ||
} | ||
|
||
function _callAndEnsureValidDeltas( | ||
address sender, | ||
PoolKey calldata key, | ||
IPoolManager.ModifyLiquidityParams calldata params, | ||
BalanceDelta delta, | ||
bytes calldata hookData | ||
) external returns (BalanceDelta) { | ||
(bool success, bytes memory returnData) = address(implementation).delegatecall( | ||
abi.encodeWithSelector(this.afterRemoveLiquidity.selector, sender, key, params, delta, hookData) | ||
); | ||
if (!success) { | ||
revert FailedImplementationCall(); | ||
} | ||
(bytes4 selector, BalanceDelta returnDelta) = abi.decode(returnData, (bytes4, BalanceDelta)); | ||
if (selector != BaseHook.afterRemoveLiquidity.selector) { | ||
revert Hooks.InvalidHookResponse(); | ||
} | ||
uint256 nonzeroDeltaCount = manager.getNonzeroDeltaCount(); | ||
if (nonzeroDeltaCount == 0 && returnDelta == BalanceDeltaLibrary.ZERO_DELTA) { | ||
return returnDelta; | ||
} | ||
if ( | ||
returnDelta.amount0() > int256(uint256(int256(delta.amount0())) * maxFeeBips / MAX_BIPS) | ||
|| returnDelta.amount1() > int256(uint256(int256(delta.amount1())) * maxFeeBips / MAX_BIPS) | ||
) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. throw this in a library to make this code path more readable! and since you are doing this calculation twice.. maybe something like: feeTake0, feeTaken1 = returnDelta.calculateFeesFrom(delta) |
||
revert HookTookTooMuchFee(); | ||
} | ||
// error on overflow | ||
returnDelta - delta; | ||
uint256 nonzeroHookDeltaCount; | ||
int256 hookDelta = manager.currencyDelta(address(this), key.currency0); | ||
if (hookDelta != 0) { | ||
if (-hookDelta != returnDelta.amount0()) { | ||
revert HookInvalidDeltasAfterRemove(); | ||
} | ||
nonzeroHookDeltaCount++; | ||
if (nonzeroHookDeltaCount == nonzeroDeltaCount) { | ||
return returnDelta; | ||
} | ||
} | ||
hookDelta = manager.currencyDelta(address(this), key.currency1); | ||
if (hookDelta != 0) { | ||
if (-hookDelta != returnDelta.amount1()) { | ||
revert HookInvalidDeltasAfterRemove(); | ||
} | ||
nonzeroHookDeltaCount++; | ||
if (nonzeroHookDeltaCount == nonzeroDeltaCount) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this not the same code twice? also Im confused what this is checking? put into helper to reduce code repetition |
||
return returnDelta; | ||
} | ||
} | ||
|
||
// weird edge case in case the hook settled the caller's deltas | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are you planning on removing? i think we should probably be ok with a hook settling a callers deltas (thats safe behavior) |
||
// can prob delete this | ||
// if (manager.currencyDelta(sender, key.currency0) != 0) { | ||
// nonzeroHookDeltaCount++; | ||
// } | ||
// if (manager.currencyDelta(sender, key.currency1) != 0) { | ||
// nonzeroHookDeltaCount++; | ||
// } | ||
// if (nonzeroHookDeltaCount == nonzeroDeltaCount) { | ||
// return returnDelta; | ||
// } | ||
|
||
revert HookInvalidDeltasAfterRemove(); | ||
} | ||
|
||
function _ensureValidFlags(address _impl) internal view virtual override { | ||
if (uint160(address(this)) & Hooks.ALL_HOOK_MASK != uint160(_impl) & Hooks.ALL_HOOK_MASK) { | ||
revert FlagsMismatch(); | ||
} | ||
if (!IHooks(address(this)).hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)) { | ||
HookPermissionForbidden.selector.revertWith(address(this)); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason the implementation needs to have the same flags as the middleware?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, will remove this actually