# Withdrawals can be locked forever if recipient is a contract

Renzo WithdrawQueue uses transfer() sending 2300 gas, so contract/multisig recipients with gas-hungry receive() can never claim, locking withdrawn ETH forever.

- 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/612
- Library page: https://bugbountylibrary.com/r/c4%3A2024-04-renzo%3AH-01
- Tags: domain:web3 impact:dos impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity vuln:dos

**Target:** WithdrawQueue.sol claim() ETH payout
**Root cause:** claim() sends ETH via payable(msg.sender).transfer(), forwarding only 2300 gas; contract recipients needing more gas in receive() revert. Because claim() requires msg.sender to equal the withdraw() requester, a contract account can never successfully claim.
**Steps:**
1. Contract/multisig calls `withdraw()` for 10 ETH worth of ezETH; contract address saved as requester.
2. After cooldown, contract calls `claim()`.
3. `payable(msg.sender).transfer(amount)` forwards 2300 gas; multisig receive() needs >6k gas, reverts OOG.
4. ETH remains locked in WithdrawQueue with no alternate claim path.
**How found:** Noticed claim() uses deprecated transfer() with 2300 gas stipend; combined with the two-step withdraw/claim flow where msg.sender is fixed, realized contract wallets (e.g. Safes needing >6k gas) would permanently fail to claim.
**Insight:** When a payout function requires msg.sender to match the original requester, a 2300-gas transfer() becomes permanent fund lock for contract wallets rather than a mere failed tx — check two-step flows for gas-stipend incompatibility.
