# ETH withdrawals from EigenLayer always fail due to OperatorDelegator's nonReentrant receive()

OperatorDelegator.completeQueuedWithdrawal is nonReentrant, but EigenLayer's ETH payout reenters receive() which is also nonReentrant, so all ETH withdrawals revert and funds stay stuck.

- Payout: Contest reward
- Severity: high
- Program: Renzo
- Credited: LessDupes
- Disclosed: 2024-06-07
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2024-04-renzo-findings/issues/368
- Library page: https://bugbountylibrary.com/r/c4%3A2024-04-renzo%3AH-03
- Tags: domain:web3 impact:dos impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:dos vuln:reentrancy

**Target:** OperatorDelegator.sol (EigenLayer delegation contract)
**Root cause:** completeQueuedWithdrawal() and receive() both carry the nonReentrant modifier, but completing a withdrawal with receiveAsTokens=true legitimately reenters receive() via EigenPod, so the guard reverts the required callback.
**Steps:**
1. Call `completeQueuedWithdrawal(withdrawal, tokens, middlewareTimesIndex)` as onlyNativeEthRestakeAdmin with a withdrawal containing ETH strategies.
2. Internally `delegationManager.completeQueuedWithdrawal(..., true)` sends ETH to the EigenPod, which forwards to OperatorDelegator.receive().
3. receive() is `nonReentrant` and the outer call already holds the lock, so the ETH transfer reverts.
4. Withdrawal cannot complete; ETH remains stuck in EigenLayer.
**How found:** Code reading: noticed both completeQueuedWithdrawal and receive() are nonReentrant, then reasoned that EigenLayer's receiveAsTokens=true path legitimately reenters receive() via EigenPod, making the guard fatal rather than protective.
**Insight:** When a contract both initiates an external call and receives a callback in the same flow, a blanket nonReentrant on the receive/callback function breaks the legitimate reentrancy; check whether the callback is required for the flow to succeed.
