-
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 14 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 |
---|---|---|
|
@@ -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,95 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.24; | ||
|
||
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; | ||
import {BalanceDelta} 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 {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 {IExttload} from "@uniswap/v4-core/src/interfaces/IExttload.sol"; | ||
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; | ||
import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; | ||
|
||
contract MiddlewareRemove is BaseMiddleware { | ||
using CustomRevert for bytes4; | ||
using Hooks for IHooks; | ||
using StateLibrary for IPoolManager; | ||
using PoolIdLibrary for PoolKey; | ||
|
||
error HookPermissionForbidden(address hooks); | ||
error HookModifiedPrice(); | ||
error HookModifiedDeltas(); | ||
error FailedImplementationCall(); | ||
|
||
bytes internal constant ZERO_BYTES = bytes(""); | ||
uint256 public constant GAS_LIMIT = 10_000_000; | ||
|
||
constructor(IPoolManager _manager, address _impl) BaseMiddleware(_manager, _impl) {} | ||
|
||
function beforeRemoveLiquidity( | ||
address sender, | ||
PoolKey calldata key, | ||
IPoolManager.ModifyLiquidityParams calldata params, | ||
bytes calldata hookData | ||
) external returns (bytes4) { | ||
address(this).delegatecall{gas: GAS_LIMIT}(abi.encodeWithSelector(this._callAndEnsurePrice.selector, msg.data)); | ||
return BaseHook.beforeRemoveLiquidity.selector; | ||
} | ||
|
||
function _callAndEnsurePrice(bytes calldata data) external { | ||
(, PoolKey memory key,,) = abi.decode(data[4:], (address, PoolKey, IPoolManager.ModifyLiquidityParams, bytes)); | ||
|
||
(uint160 priceBefore,,,) = manager.getSlot0(key.toId()); | ||
(bool success,) = address(implementation).delegatecall(data); | ||
if (!success) { | ||
revert FailedImplementationCall(); | ||
} | ||
(uint160 priceAfter,,,) = manager.getSlot0(key.toId()); | ||
if (priceAfter != priceBefore) { | ||
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. this is an awesome check! Similar to my comment below I wonder if for each check we can abstract them out. And then have a Middlewear contract that inherits any combination of these checks to allow for all sorts of combinations of checks 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. that would be cool, like lego blocks you can stack to create middleware checks. but is this level of modularity really necessary? i feel like there are only a few middleware designs that truly make sense. and let's say uniswap frontend decides to whitelist middleware-remove hooks for example. there's not really any incentive for new devs to make new middleware designs, as one is all we really need. unless... the plan is to make these protections modular on the frontend. eg: middleware XYZ has these permissions but is blocked from these middleware selections. almost like an anti-virus checklist that shows up per hook. this would give so much flexibility to the hook developer! 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. yeah exactly I like the lego block metaphor |
||
// purpousely revert to cause the whole hook to reset | ||
revert HookModifiedPrice(); | ||
} | ||
} | ||
|
||
function afterRemoveLiquidity( | ||
address sender, | ||
PoolKey calldata key, | ||
IPoolManager.ModifyLiquidityParams calldata params, | ||
BalanceDelta delta, | ||
bytes calldata hookData | ||
) external returns (bytes4, BalanceDelta) { | ||
address(this).delegatecall{gas: GAS_LIMIT}( | ||
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 there a reason to delegatecall here? Is it cause we need the GAS_LIMIT enforcement for the whole remainder or the call? What if we just enforced that gasLeft() > GAS_LIMIT by some proportion so that we could internally call 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. yes, so this is a solution that took me a while to figure out. it looks strange, but i need to perform a try catch and keep msg.sender the same. you can't try catch an internal function, and if i do .call instead of .delegatecall, msg.sender is now the middleware. 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. where is the try/catch? 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. oh well technically not a try/catch. it just doesn't require success |
||
abi.encodeWithSelector(this._callAndEnsureZeroDeltas.selector, msg.data) | ||
); | ||
return (BaseHook.afterRemoveLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); | ||
} | ||
|
||
function _callAndEnsureZeroDeltas(bytes calldata data) external { | ||
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. Can't we just not allow the hook to have the flags for also I can semi see a case where we'd just want to protect no reverts on remove, but not necessarily no deltas? I wonder if we could split out these two implementations and then have a third implementation that inherits them both? 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. so i already have a flag check in the _ensureValidFlags override. (we don't care about add liquidity btw) (while typing this i realized that i also need to handle this in beforeRemoveLiquidity as well) 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. ah gotcha - maybe a comment in the contract explaining this? However this problem is not really solved by the middlewear I think. IE... it doesn't really matter if we revert here or revert from within the hook, the user still won't be able to remove their liquidity... 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. it actually just reverts the entire hook call. see my testFeeOnRemove hook, how the swap still goes through |
||
bytes32 slot = bytes32(NonZeroDeltaCount.NONZERO_DELTA_COUNT_SLOT); | ||
uint256 countBefore = uint256(IExttload(address(manager)).exttload(slot)); | ||
(bool success,) = address(implementation).delegatecall(data); | ||
if (!success) { | ||
revert FailedImplementationCall(); | ||
} | ||
uint256 countAfter = uint256(IExttload(address(manager)).exttload(slot)); | ||
if (countAfter != countBefore) { | ||
// purpousely revert to cause the whole hook to reset | ||
revert HookModifiedDeltas(); | ||
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. What exactly are you trying to prevent with this check? A hook could technically still modify deltas on the pool so long as they settle it back to the count before the call. 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. it's totally ok if the hook modifies deltas as long as they settle it. as i mentioned previously, the goal is to catch reverts when a hook incorrectly settles deltas. if countAfter != countBefore, it's a heuristic that the hook messed something up, in which the entire thing reverts (see why i needed two delegatecalls?) and it becomes a vanilla hookless withdrawal. |
||
} | ||
} | ||
|
||
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)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.19; | ||
|
||
import {BaseMiddlewareFactory} from "./BaseMiddlewareFactory.sol"; | ||
import {MiddlewareRemove} from "./MiddlewareRemove.sol"; | ||
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; | ||
|
||
contract MiddlewareRemoveFactory is BaseMiddlewareFactory { | ||
constructor(IPoolManager _manager) BaseMiddlewareFactory(_manager) {} | ||
|
||
function _deployMiddleware(address implementation, bytes32 salt) internal override returns (address middleware) { | ||
middleware = address(new MiddlewareRemove{salt: salt}(manager, implementation)); | ||
} | ||
} |
+0 −1 | .forge-snapshots/add.snap | |
+0 −1 | .forge-snapshots/addClosure.snap | |
+1 −1 | .forge-snapshots/checkManyAdd.snap | |
+1 −1 | .forge-snapshots/checkSize.snap | |
+1 −1 | .forge-snapshots/internalClosure.snap | |
+1 −1 | .forge-snapshots/manyAdd.snap | |
+1 −1 | .forge-snapshots/manySstore.snap | |
+1 −0 | .forge-snapshots/singleSstore.snap | |
+1 −0 | .forge-snapshots/singleSstoreClosure.snap | |
+1 −0 | .forge-snapshots/singleSstoreLastCall.snap | |
+1 −1 | .forge-snapshots/sizeTarget.snap | |
+1 −1 | .forge-snapshots/sstoreClosure.snap | |
+1 −1 | .github/workflows/test.yml | |
+21 −0 | LICENSE | |
+1 −1 | lib/forge-std | |
+11 −2 | src/GasSnapshot.sol | |
+4 −0 | src/test/SimpleOperations.sol | |
+5 −2 | src/utils/UintString.sol | |
+31 −23 | test/GasSnapshot.t.sol |
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