The OperatorDelegator.completeQueuedWithdrawal() function serves to finalize a queued withdrawal consisting of different tokens, sending the withdrawn tokens to the WithdrawQueue contract up to the buffer amount, and depositing any excess tokens back into the corresponding EigenLayer strategy.
The issue is that the call to strategyManager.depositIntoStrategy() used to deposit any amount of excess tokens back into EigenLayer may revert, which would cause the entire completeQueuedWithdrawal() transaction to revert.
There are a couple reasons why depositIntoStrategy() may revert:
- In
StrategyManager.depositIntoStrategy(), the number of shares returned by the strategy is verified to be> 0. If the strategy mints shares at less than a 1:1 ratio, this can cause the transaction to revert, as the amount being deposited back may be as small as 1 wei. - EigenLayer's stETH and wBETH strategies both implement per-transaction and total deposit limits (stETH, wBETH):
- If the deposit limit of a strategy has been reached, attempting to complete a queued withdrawal containing any amount over the
WithdrawQueue's buffer of the strategy's token will fail. - If the amount being redeposited is large enough, it may surpass the per-deposit limit.
This issue forces the withdrawQueueAdmin (which is a separate entity from the nativeEthRestakeAdmin affected) to set the buffer for the affected token high enough for the full amount being withdrawn to be transferred to the WithdrawQueue. This will require a third party to intervene and the protocol to handle in an unintended way by increasing the withdraw buffer for one or more tokens.
Furthermore, another entry point that calls strategyManager.depositIntoStrategy() and will revert under the same conditions is RestakeManager.deposit(), the main deposit function for collateral tokens. Also here, the collateral token being deposited is first used to fill the WithdrawQueue's buffer, so the transaction can revert for any of the reasons outlined above.
Proof of Concept
Scenario A:
- The
nativeEthRestakeAdminqueues a withdrawal for 10 ETH, 10 stETH and 10 wBETH. - After the withdrawal delay passes,
completeQueuedWithdrawal()is called to process the withdrawals. - The 10 ETH are forwarded to the deposit queue.
- Next, 9 stETH are withdrawn and sent to
WithdrawQueue, filling its stETH buffer. - 1 stETH remains and is attempted to be deposited back into the stETH strategy via
strategyManager.depositIntoStrategy(). - However, the stETH strategy has already reached its total deposit limit.
- The deposit reverts and causes the entire
completeQueuedWithdrawal()transaction to revert.
Scenario B:
- A user calls
RestakeManager.deposit()to deposit 1 wBETH. - All wBETH but 1 wei is used to fill the wBETH buffer in the
WithdrawQueue. - The remaining 1 wei is then attempted to be deposited into the wBETH strategy via
strategyManager.depositIntoStrategy(). - However, it is converted into
0shares by the strategy. StrategyManagerverifies that the number of shares minted by the strategy is greater than0.- Since
0shares were minted, this check fails. - The
strategyManager.depositIntoStrategy()call reverts. - This causes the entire
RestakeManager.deposit()transaction to revert due to no fault of the user's.
Recommended Mitigation Steps
Consider catching any reverts when depositing excess tokens into strategies. In order to ensure that the excess tokens remain in the system and are accounted for in the TVL, the best option may be to send them to the WithdrawQueue in the catch clause, regardless of whether the buffer for the given token is already full.