-
Notifications
You must be signed in to change notification settings - Fork 39
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
Refactoring addLiquidityUnbalancedNestedPool in CLR #1203
base: main
Are you sure you want to change the base?
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. Just a few comments, otherwise makes sense.
Other than code specific comments, just a few generic comments:
- I don't think we want to support nesting beyond 1 level. That makes me feel a bit reluctant to use recursion. The implementation here is rather simple, but we've had instances in the past where being too flexible ended up being counterproductive. I'd give it a second thought, maybe just using a function that goes down only one level instead of full blown recursion.
- I get that the case described in the PR title is being covered. What I'd like to see is an explicit test where a pool has mixed tokens (e.g. child pool has sDAI and waUSDT) and the user is able to add liquidity using sDAI (wrapped) and USDT (underlying), which is the case we don't support today (in the current main we must add dai and usdt).
} else if (_settledTokenAmounts().tGet(childToken) == 0) { | ||
amountsIn[i] = _currentSwapTokenInAmounts().tGet(childToken); | ||
_settledTokenAmounts().tSet(childToken, amountsIn[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.
I don't fully get this last if
clause. Could you please add some comments here? Why are we checking settled token amounts?
We are covering:
- tokens that are pools (we keep adding recursively)
- wrapper case: user wants to add using underlying (buffer is initialized, there is no wrapped amount specified)
I understand the missing case is user wants to add with wrapped tokens, but I think that's not necessarily the case when you get here. User could specify underlying, but if the buffer is not initialized you'll still end up here.
Sounds like the case where bufferInitialized == false
and user specifies underlying should revert.
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.
There are three cases being covered:
- tokens that are pools (we keep adding recursively)
- wrapper case: user wants to add using underlying (buffer is initialized, there is no wrapped amount specified)
- Normal ERC20 tokens
If the buffer is not initialized, that token may or may not be ERC4626, we treat it as a normal ERC20 token.
The if
is in there to make sure the token amount was not spent in a previous child pool. In the previous solution I modified the token in amounts directly, so the if was not needed, but then I needed to restore the token in amounts array at the end. This solution is better.
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 mostly LGTM, but I'm worried too with the recursion. I mean, the Vault is safe, but the router can behave unpredictably if there's a cyclic path where the same pool appears multiple times. Maybe it reverts, maybe it adds zero tokens, but what happens with the transient accounting? I believe we'd need to check that case if we want to proceed with the unlimited recursion.
We use almost the same code inside these two functions ( |
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'm not sure I have full context on all this, but tried to clarify some things in the comments.
} | ||
|
||
IERC20[] memory parentPoolTokens = _vault.getPoolTokens(params.pool); | ||
(uint256[] memory amountsIn, ) = _addLiquidityRecursive(params.pool, params); |
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.
At first glance it looks like you could just have _addLiquidityRecursive
take a single parameter, since the pool is already in the params - but you can't, because sometimes it's the child pool, different from the one in params. Would be good to document that somewhere (maybe where the function's defined).
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.
We might also note that the Vault explicitly prohibits "Linear pool-style" recursion, where a pool contains its own BPT, so that case is impossible and we don't need to handle it.
} | ||
} | ||
|
||
function _addLiquidityRecursive( |
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.
function _addLiquidityRecursive( | |
function _addLiquidity( |
Rather than overloading _addLiquidityRecursive, consider naming the "outer" one _addLiquidity, leaving the "inner" one (that is actually recursive, with the level argument) as _addLiquidityRecursive.
@@ -32,6 +32,8 @@ contract CompositeLiquidityRouter is ICompositeLiquidityRouter, BatchRouterCommo | |||
using TransientEnumerableSet for TransientEnumerableSet.AddressSet; | |||
using TransientStorageHelpers for *; | |||
|
|||
uint256 constant _MAX_LEVEL_IN_NESTED_OPERATIONS = 2; |
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.
uint256 constant _MAX_LEVEL_IN_NESTED_OPERATIONS = 2; | |
// The level is 1-based, so the outermost pool (level 1) can contain a nested BPT, but any pool tokens in the child | |
// pool (level 2) will not be expanded further, but simply treated as standard tokens. | |
uint256 constant _MAX_LEVEL_IN_NESTED_OPERATIONS = 2; |
Clarify what 2 levels means (there was some confusion off-line).
params.sender, | ||
params.wethIsEth | ||
if (level > _MAX_LEVEL_IN_NESTED_OPERATIONS) { | ||
// if we have reached the maximum level of nested operations, the token will be calculated as ERC20 |
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.
// if we have reached the maximum level of nested operations, the token will be calculated as ERC20 | |
// If we have exceeded the maximum level of nested operations, the token will be considered a standard ERC20. |
_wrapAndUpdateTokenInAmounts(IERC4626(childToken), params.sender, params.wethIsEth); | ||
amountsIn[i] = _wrapAndUpdateTokenInAmounts(IERC4626(childToken), params.sender, params.wethIsEth); | ||
} else if (_settledTokenAmounts().tGet(childToken) == 0) { | ||
// if this token is ERC20 and the amount was not settled in a previous operation, it should be added |
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.
// if this token is ERC20 and the amount was not settled in a previous operation, it should be added | |
// Set this token's amountIn if it's a standard token that was not previously settled. |
// Remove the underlying token from the tokensIn and set the amount as zero because we took it here. | ||
// We will take other tokens at the end of the calculation. |
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.
// Remove the underlying token from the tokensIn and set the amount as zero because we took it here. | |
// We will take other tokens at the end of the calculation. | |
// Remove the underlying token from `_currentSwapTokensIn` and zero out the amount, as these tokens were paid | |
// in advance and wrapped. Remaining tokens will be transferred in at the end of the calculation. |
Is this correct? I wasn't sure what "taking the token" meant in the original.
Description
This refactoring fixes implicit recursion by turning it into explicit recursion.
Type of change
Checklist:
main
, or there's a description of how to mergeIssue Resolution