Skip to content
Request an audit

‹ All findings

Withdrawals can fail due to deposits reverting in completeQueuedWithdrawal()

MediumRenzo·Code4rena · Liquid restaking · 30th April 2024CodebaseM-01

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:

  1. 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.
  2. 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:

  1. The nativeEthRestakeAdmin queues a withdrawal for 10 ETH, 10 stETH and 10 wBETH.
  2. After the withdrawal delay passes, completeQueuedWithdrawal() is called to process the withdrawals.
  3. The 10 ETH are forwarded to the deposit queue.
  4. Next, 9 stETH are withdrawn and sent to WithdrawQueue, filling its stETH buffer.
  5. 1 stETH remains and is attempted to be deposited back into the stETH strategy via strategyManager.depositIntoStrategy().
  6. However, the stETH strategy has already reached its total deposit limit.
  7. The deposit reverts and causes the entire completeQueuedWithdrawal() transaction to revert.

Scenario B:

  1. A user calls RestakeManager.deposit() to deposit 1 wBETH.
  2. All wBETH but 1 wei is used to fill the wBETH buffer in the WithdrawQueue.
  3. The remaining 1 wei is then attempted to be deposited into the wBETH strategy via strategyManager.depositIntoStrategy().
  4. However, it is converted into 0 shares by the strategy.
  5. StrategyManager verifies that the number of shares minted by the strategy is greater than 0.
  6. Since 0 shares were minted, this check fails.
  7. The strategyManager.depositIntoStrategy() call reverts.
  8. 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.