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

Before after add/remove liquidity, remove all modifyPosition hooks #434

Merged
merged 31 commits into from
Dec 14, 2023

Conversation

rileydcampbell
Copy link
Contributor

@rileydcampbell rileydcampbell commented Dec 6, 2023

Which issue does this pull request resolve?

This PR fixes #350. The main idea is:

Right now, beforeModifyPosition is a risky permission. Liquidity providers need to audit and trust the hook code or risk never being able to withdraw their money. That makes it harder to fearlessly integrate pools that have this permission.

Description of changes

We broke up the modify position hook into an addLiquidity and removeLiquidity hook. This allows for a better separation of permissions. The following changes were made to implement these hooks.

  • Add before/after addLiquidity
  • Add before/after removeLiquidity
  • Remove modifyPosition hooks
  • Update existing tests and added new ones

@rileydcampbell rileydcampbell force-pushed the before-after-add/removeLiquidity branch from 7ec6d8d to cee4d82 Compare December 7, 2023 22:31
Comment on lines 155 to 157
// TODO: Maybe due to a rounding issue, but balanceOfBefore0 == balanceOfAfter0 ?
assertTrue(balanceOfBefore0 - balanceOfAfter0 <= 1);
assertTrue(balanceOfBefore1 - balanceOfAfter1 - amount <= 1);
Copy link
Contributor Author

@rileydcampbell rileydcampbell Dec 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why I had an off by one issue here? Is it possible that it is a rounding issue?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya should equal, that's werid

@rileydcampbell rileydcampbell marked this pull request as ready for review December 7, 2023 22:39
Copy link
Contributor

@hensha256 hensha256 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like theres improvements in efficiency of the internal code that can be made now that we have separation of add/remove liquidity functions.

For example, in the Pool library in modifyPosition, it has to do checks

if (params.liquidityDelta > 0) {
   /// aka if this is an ADD liquidity then do X
} 
if (params.liquidityDelta < 0) {
   /// aka if this is a REMOVE liquidity then do Y
}

this PR gives the nice separation of entry points, but then still leads to the same internal flow of checking whether liquidityDelta is positive or negative, even though that is actually known in advance

I think we should maybe investigate splitting pool logic into internal addLiquidity and removeLiquidity, where removeLiquidity never has to do any of the params.liquidityDelta > 0 checks and vice versa?

@rileydcampbell
Copy link
Contributor Author

@hensha256 Definitely agree! I can take a closer look at the Pool logic to see how that might simplify things. This would be as a follow up yeah? Not blocking

Copy link
Member

@snreynolds snreynolds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like a good structure although hard to give a full review knowing a lot of this will change with Alice's and Mark's changes... could you pull those in? 😄

src/PoolManager.sol Outdated Show resolved Hide resolved
src/interfaces/IPoolManager.sol Outdated Show resolved Hide resolved
src/PoolManager.sol Outdated Show resolved Hide resolved
@rileydcampbell
Copy link
Contributor Author

seems like a good structure although hard to give a full review knowing a lot of this will change with Alice's and Mark's changes... could you pull those in? 😄

Yeah will do! Might take me a sec to do the refactor/fix all the merge conflicts

@hensha256
Copy link
Contributor

This would be as a follow up yeah? Not blocking

Yeah we can make it a followup, please can you open a github issue so we dont forget?

src/PoolManager.sol Outdated Show resolved Hide resolved
@rileydcampbell rileydcampbell force-pushed the before-after-add/removeLiquidity branch from b9f2398 to 832e6b1 Compare December 12, 2023 02:50
Comment on lines 170 to 186
) internal returns (bool shouldExecute) {
if (self.hasPermission(BEFORE_MODIFY_POSITION_FLAG)) {
shouldExecute = self.callHookNoopable(
abi.encodeWithSelector(IHooks.beforeModifyPosition.selector, msg.sender, key, params, hookData)
);
if (params.liquidityDelta >= 0) {
if (key.hooks.hasPermission(BEFORE_ADD_LIQUIDITY_FLAG)) {
shouldExecute = self.callHookNoopable(
abi.encodeWithSelector(IHooks.beforeAddLiquidity.selector, msg.sender, key, params, hookData)
);
} else {
shouldExecute = true;
}
} else {
return true;
if (key.hooks.hasPermission(BEFORE_REMOVE_LIQUIDITY_FLAG)) {
shouldExecute = self.callHookNoopable(
abi.encodeWithSelector(IHooks.beforeRemoveLiquidity.selector, msg.sender, key, params, hookData)
);
} else {
shouldExecute = true;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel like this looks a little ugly, could also change to something like:

    /// @notice calls beforeModifyPosition hook if permissioned and validates return value
    function beforeModifyPosition(
        IHooks self,
        PoolKey memory key,
        IPoolManager.ModifyPositionParams memory params,
        bytes calldata hookData
    ) internal returns (bool shouldExecute) {
        if (params.liquidityDelta >= 0 && key.hooks.hasPermission(BEFORE_ADD_LIQUIDITY_FLAG)) {
            shouldExecute = self.callHookNoopable(
                abi.encodeWithSelector(IHooks.beforeAddLiquidity.selector, msg.sender, key, params, hookData)
            );
        } else if (params.liquidityDelta < 0 && key.hooks.hasPermission(BEFORE_REMOVE_LIQUIDITY_FLAG)) {
            shouldExecute = self.callHookNoopable(
                abi.encodeWithSelector(IHooks.beforeRemoveLiquidity.selector, msg.sender, key, params, hookData)
            );
        } else {
            shouldExecute = true;
        }
    }

It's a little more gas ~100 but maybe that's ok?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should expose separate getters for querying beforeAdd/RemoveLiquidity and deprecating beforeModifiyPosition + afterModifyPosition so you don't need this conditional

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I prefer this branching. And @zhongeric I think Mark and I talked about this and we kinda liked having the beforeModifyPos/afterModifyPos as the entrypoints in the Hooks library so that we are removing the liquidityDelta logic from core codepaths

Comment on lines 170 to 186
) internal returns (bool shouldExecute) {
if (self.hasPermission(BEFORE_MODIFY_POSITION_FLAG)) {
shouldExecute = self.callHookNoopable(
abi.encodeWithSelector(IHooks.beforeModifyPosition.selector, msg.sender, key, params, hookData)
);
if (params.liquidityDelta >= 0) {
if (key.hooks.hasPermission(BEFORE_ADD_LIQUIDITY_FLAG)) {
shouldExecute = self.callHookNoopable(
abi.encodeWithSelector(IHooks.beforeAddLiquidity.selector, msg.sender, key, params, hookData)
);
} else {
shouldExecute = true;
}
} else {
return true;
if (key.hooks.hasPermission(BEFORE_REMOVE_LIQUIDITY_FLAG)) {
shouldExecute = self.callHookNoopable(
abi.encodeWithSelector(IHooks.beforeRemoveLiquidity.selector, msg.sender, key, params, hookData)
);
} else {
shouldExecute = true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should expose separate getters for querying beforeAdd/RemoveLiquidity and deprecating beforeModifiyPosition + afterModifyPosition so you don't need this conditional

src/test/NoOpTestHooks.sol Outdated Show resolved Hide resolved
Comment on lines 155 to 157
// TODO: Maybe due to a rounding issue, but balanceOfBefore0 == balanceOfAfter0 ?
assertTrue(balanceOfBefore0 - balanceOfAfter0 <= 1);
assertTrue(balanceOfBefore1 - balanceOfAfter1 - amount <= 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya should equal, that's werid

test/AccessLock.t.sol Outdated Show resolved Hide resolved
test/AccessLock.t.sol Show resolved Hide resolved
Copy link
Member

@snreynolds snreynolds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last overall

Should we be hitting the beforeAddLiquidity logic for liquidityParams of 0? I think in the worst case someone could revert on just collecting fees but not wanting to change their position. Either we don't call out to hooks or maybe have the 0 liquidityParams branch out to the afterAddLiquidity branch?

Also should we change name to modifyLiquidity()?

@rileydcampbell
Copy link
Contributor Author

I removed any of the add/remove liquidity hook calls for a liquidityDelta == 0.

@rileydcampbell rileydcampbell force-pushed the before-after-add/removeLiquidity branch from 0db7782 to 8c5e457 Compare December 13, 2023 21:42
Copy link
Member

@snreynolds snreynolds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just last name change suggestion but looks rlly good!

/// @param params The parameters for modifying the liquidity
/// @param hookData Any data to pass to the callback, via `ILockCallback(msg.sender).lockAcquired(data)`
/// @return delta The balance delta of the liquidity
function modifyLiquidity(PoolKey memory key, ModifyPositionParams memory params, bytes calldata hookData)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

laaaast lil nit: ModifyLiquidityParams ?

@@ -161,12 +168,16 @@ library Hooks {
IPoolManager.ModifyPositionParams memory params,
bytes calldata hookData
) internal returns (bool shouldExecute) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and same name change on these functions? Just thinking so that they are all standardized

beforeModifyPosition -> beforeModifyLiquidity
afterModifyPosition -> afterModifyLiquidity

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed 👍

Copy link
Member

@snreynolds snreynolds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚢

@rileydcampbell rileydcampbell merged commit 36160ce into main Dec 14, 2023
4 checks passed
@rileydcampbell rileydcampbell deleted the before-after-add/removeLiquidity branch December 14, 2023 19:41
hyunchel pushed a commit to hyunchel/v4-core that referenced this pull request Feb 21, 2024
…niswap#434)

* Add before/after add/remove liquidity hooks

---------

Co-authored-by: Eric Zhong <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Split {before,after}ModifyPosition into {before,after}Mint and {before,after}Burn
6 participants