-
Notifications
You must be signed in to change notification settings - Fork 40
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
Using wrappedToken in CompositeLiquidityRouter in ERC4626Pool operations #1201
base: main
Are you sure you want to change the base?
Using wrappedToken in CompositeLiquidityRouter in ERC4626Pool operations #1201
Conversation
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.
First pass done. I didn't get to the tests yet, but I think there are a few things that need to be adjusted before proceeding.
Overall the idea is correct; great job. It's just that some of the edge cases should behave differently now that we're not automatically wrapping / unwrapping.
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.
Another pass done.
Changes are looking good for the most part. Just a few minor comments around names / docs / style; otherwise seems to be correct. I'll finish covering the tests in one more pass. Great job!
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.
Looks like a pretty elegant solution (especially to the limits problem). I have a conceptual issue with the useWrappedTokens
name, and lots of little details.
I also think these PRs need a lot more in the description. Why do we need it? What problem does it address? What options did we consider and reject (e.g., in this case, the limits hack, replaced by splitting the wrap/unwrap functions)?
Otherwise we're not going to remember all this context when we go back and look at this in 6 months or a year.
Co-authored-by: EndymionJkb <[email protected]>
Co-authored-by: EndymionJkb <[email protected]>
Co-authored-by: EndymionJkb <[email protected]>
Co-authored-by: EndymionJkb <[email protected]>
Co-authored-by: EndymionJkb <[email protected]>
Co-authored-by: EndymionJkb <[email protected]>
Co-authored-by: EndymionJkb <[email protected]>
Co-authored-by: EndymionJkb <[email protected]>
Co-authored-by: EndymionJkb <[email protected]>
Co-authored-by: EndymionJkb <[email protected]>
…ttps://github.com/balancer/balancer-v3-monorepo into using-wrapped-amount-in-erc4626-operations-in-clr
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.
I'd recommend changing isWrappedToken
to useWrappedToken
, it's probably clearer. Also, since the amounts out are mixing wrapped and underlying, I'd return a token array, for UX purposes. Not sure if gas is a constraint in this case.
Ohh, I just noticed it was |
I didn't have an issue with the name (also prefer Maybe reverse the sense of the name to make it clearer, like |
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.
I think we need to address the boolean array name (see my suggestion to use isStandardToken
or useAsStandardToken
), and returning the token addresses, which would resolve all the "sorted in ... order" comment issues.
My eyes are already lost in the description, I double checked several times, but if you can double check, I would be very grateful |
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.
Looking much better! Note the suggested language for NatSpec, which I believe is much clearer.
* @param useAsStandardToken An array indicating whether to use the token as standard or wrap it, | ||
* sorted in token registration order of wrapped tokens in the pool | ||
* @param exactAmountsIn Exact amounts of underlying/wrapped tokens in, sorted in token registration order | ||
* wrapped tokens in the pool |
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.
* @param useAsStandardToken An array indicating whether to use the token as standard or wrap it, | |
* sorted in token registration order of wrapped tokens in the pool | |
* @param exactAmountsIn Exact amounts of underlying/wrapped tokens in, sorted in token registration order | |
* wrapped tokens in the pool | |
* @param useAsStandardToken Flags indicating whether the corresponding token should be treated as an ERC4626 | |
* (transfer the underlying asset and wrap) or as a standard ERC20 (transfer the wrapped token directly) | |
* @param exactAmountsIn Exact amounts of underlying/wrapped tokens in, sorted in token registration order |
I think this spells it out. The tokens are specified in registration order, the only question is whether they'll be used directly or interpreted as underlying instead.
You could also have non-ERC4626 tokens (where the flag would be true), which is why I don't think "order of wrapped tokens" is appropriate. It's just the order of tokens as they were registered.
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.
It's not in the diff, but we also need to fix other references to underlying tokens, such as the notice of this function:
"Add arbitrary amounts of underlying tokens to an ERC4626 pool through the buffer."
Generally "underlying tokens" should just be "tokens" where it appears (e.g., notice NatSpec for add/remove functions)
* @param useAsStandardToken An array indicating whether to use the token as standard or wrap it, | ||
* sorted in token registration order of wrapped tokens in the pool | ||
* @param maxAmountsIn Maximum amounts of underlying/wrapped tokens in, sorted in token registration order | ||
* wrapped tokens in the pool |
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.
* @param useAsStandardToken An array indicating whether to use the token as standard or wrap it, | |
* sorted in token registration order of wrapped tokens in the pool | |
* @param maxAmountsIn Maximum amounts of underlying/wrapped tokens in, sorted in token registration order | |
* wrapped tokens in the pool | |
* @param useAsStandardToken Flags indicating whether the corresponding token should be treated as an ERC4626 | |
* (transfer the underlying asset and wrap) or as a standard ERC20 (transfer the wrapped token directly) | |
* @param exactAmountsIn Exact amounts of underlying/wrapped tokens in, sorted in token registration order |
Same as above
* @param useAsStandardToken An array indicating whether to use the token as standard or unwrap it, | ||
* sorted in token registration order of wrapped tokens in the pool |
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.
* @param useAsStandardToken An array indicating whether to use the token as standard or unwrap it, | |
* sorted in token registration order of wrapped tokens in the pool | |
* @param useAsStandardToken Flags indicating whether the corresponding token should be treated as an ERC4626 | |
* (unwrap and transfer the underlying asset) or as a standard ERC20 (transfer the wrapped token directly) |
* @param minAmountsOut Minimum amounts of each token, sorted according to tokensIn array | ||
* wrapped tokens in the pool |
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.
* @param minAmountsOut Minimum amounts of each token, sorted according to tokensIn array | |
* wrapped tokens in the pool | |
* @param minAmountsOut Minimum amounts of each token, corresponding to `tokensOut` |
* @param useAsStandardToken An array indicating whether to use the token as standard or wrap it, | ||
* sorted in token registration order of wrapped tokens in the pool |
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.
* @param useAsStandardToken An array indicating whether to use the token as standard or wrap it, | |
* sorted in token registration order of wrapped tokens in the pool | |
* @param useAsStandardToken Flags indicating whether the corresponding token should be treated as an ERC4626 | |
* (transfer the underlying asset and wrap) or as a standard ERC20 (transfer the wrapped token directly) |
@@ -196,7 +214,7 @@ interface ICompositeLiquidityRouter { | |||
* @param tokensOut Output token addresses, sorted by user preference. `tokensOut` array must have all tokens from | |||
* child pools and all tokens that are not BPTs from the nested pool (parent pool). If not all tokens are informed, | |||
* balances are not settled and the operation reverts. Tokens that repeat must be informed only once. | |||
* @param minAmountsOut Minimum amounts of each outgoing underlying token, sorted according to tokensIn array | |||
* @param minAmountsOut Minimum amounts of each token, sorted according to tokensIn array | |||
* @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | |||
* @param userData Additional (optional) data required for the operation | |||
* @return amountsOut Actual amounts of tokens received, parallel to `tokensOut` |
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.
* @return amountsOut Actual amounts of tokens received, parallel to `tokensOut` | |
* @return amountsOut Actual amounts of tokens received, corresponding to `tokensOut` |
Parallel would work too, but trying to be consistent.
@@ -152,7 +170,7 @@ interface ICompositeLiquidityRouter { | |||
* @param parentPool Address of the highest level pool (which contains BPTs of other pools) | |||
* @param tokensIn Input token addresses, sorted by user preference. `tokensIn` array must have all tokens from | |||
* child pools and all tokens that are not BPTs from the nested pool (parent pool). | |||
* @param exactAmountsIn Amount of each underlying token in, sorted according to tokensIn array | |||
* @param exactAmountsIn Amount of each token in, sorted according to tokensIn array |
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.
* @param exactAmountsIn Amount of each token in, sorted according to tokensIn array | |
* @param exactAmountsIn Amount of each token in, corresponding to `tokensIn` |
This seems more generic, as we might be interpreting tokens differently, such that they're not necessarily sorted
@@ -172,7 +190,7 @@ interface ICompositeLiquidityRouter { | |||
* @param parentPool Address of the highest level pool (which contains BPTs of other pools) | |||
* @param tokensIn Input token addresses, sorted by user preference. `tokensIn` array must have all tokens from | |||
* child pools and all tokens that are not BPTs from the nested pool (parent pool). | |||
* @param exactAmountsIn Amount of each underlying token in, sorted according to tokensIn array | |||
* @param exactAmountsIn Amount of each token in, sorted according to tokensIn array |
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.
* @param exactAmountsIn Amount of each token in, sorted according to tokensIn array | |
* @param exactAmountsIn Amount of each token in, corresponding to `tokensIn` |
for (uint256 i = 0; i < poolTokensLength; ++i) { | ||
// Treat all ERC4626 pool tokens as wrapped. The next step will verify if we can use the wrappedToken as | ||
// a valid ERC4626. | ||
// a valid ERC4626. Note that if `useWrappedTokens[i]` is false, we will treat it as a standard token. |
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.
// a valid ERC4626. Note that if `useWrappedTokens[i]` is false, we will treat it as a standard token. | |
// a valid ERC4626. |
It's self-documenting now; don't even need a comment.
|
||
for (uint256 i = 0; i < poolTokensLength; ++i) { | ||
// Treat all ERC4626 pool tokens as wrapped. The next step will verify if we can use the wrappedToken as | ||
// a valid ERC4626. Note that if `useAsStandardToken[i]` is false, we will treat it as a standard token. |
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.
// a valid ERC4626. Note that if `useAsStandardToken[i]` is false, we will treat it as a standard token. | |
// a valid ERC4626. |
Don't need the comment (and backwards as written anyway)
Sorry for the late comment, but I feel we're making up new terms here 😅 We're already dealing with wrapped and underlying, and that should be enough. I can't know which of them is The argument is consumed by a function called |
Makes sense - especially because there isn't an unwrap, so there's only one name for the set of flags. The only drawback is the "default" value would be to set everything to true (kind of annoying), but I agree that reusing standard terms is more important. |
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.
This is looking great.
Just a few more comments; otherwise LGTM. I like the new structure much better; it reuses less code, but it's more clear and goes straight to the point.
if (amountsOut[i] < params.minAmountsOut[i]) { | ||
revert IVaultErrors.AmountOutBelowMin(underlyingToken, amountsOut[i], params.minAmountsOut[i]); | ||
} |
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.
Just for the record, I think this is not needed because we're already applying the limit in the unwrap operation, but it's fine to leave it too for consistency and readability.
AddLiquidityHookParams calldata params, | ||
function _wrapTokensExactInIfRequired( | ||
address sender, | ||
bool[] memory useAsStandardToken, | ||
IERC20[] memory erc4626PoolTokens, | ||
uint256[] memory amountsIn, |
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.
Note for self: amountsIn
is params.maxAmountsIn
, so we don't need to apply limits here.
if (amountsOut[i] < params.minAmountsOut[i]) { | ||
revert IVaultErrors.AmountOutBelowMin(underlyingToken, amountsOut[i], params.minAmountsOut[i]); | ||
} | ||
|
||
if (isStaticCall == false) { | ||
_sendTokenOut(params.sender, underlyingToken, underlyingAmountsOut[i], params.wethIsEth); | ||
if (isStaticCall == false) { | ||
_sendTokenOut(params.sender, underlyingToken, amountsOut[i], params.wethIsEth); | ||
} |
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.
Actually these 2 inner ifs are the same in the "no unwrap" and in the "wrap" case, as long as you set the token out and amount out properly.
If you put these lines inside the for loop the code might be a bit simpler.
RemoveLiquidityHookParams calldata params | ||
) external nonReentrant onlyVault returns (uint256[] memory underlyingAmountsOut) { | ||
RemoveLiquidityHookParams calldata params, | ||
bool[] calldata useAsStandardToken |
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.
About naming: in this case it would be unwrapWrapper
or unwrapWrapped
I guess
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.
The opposite of wrapUnderlying
would be unwrapWrapped
, so we do need it in both directions, and the default values would be true. (And unwrapWrapped is a bit of a tongue twister.)
Unless useAsStandardToken
-> doNotWrapOrUnwrap
or doNotUseAsWrappedToken
or doNotUseAsERC4626
(defaults to false, single name), or doNotWrap
, doNotUnwrap
(defaults to false, two names), without making up new/potentially confusing terms.
Names are hard.
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.
I don't think we have 'default values' anymore. The default in the current version is always wrap / unwrap, but that doesn't mean it has to be that way for this version. Sending false
arrays is easier than true
in solidity, but for the SDK I don't think it's a big deal either way.
I don't have a strong opinion about the tone, but I do prefer positive flags (i.e. true == do something as opposed to true == don't do something) from a readability / comprehension perspective.
Description
The current CLR implementation does not support adding mixed assets. For example, if my pool consists of two wrapped tokens, and I already have one of them and want to use it, this is not possible with the current CLR. The reason is that it treats all input tokens as underlying assets. This PR resolves the issue by introducing a new flag in the interface, allowing us to specify which type of token to use.
Type of change
Checklist:
main
, or there's a description of how to mergeIssue Resolution
Closes #1192