# Malicious user can finalize other’s withdrawal with less than specified gas limit, leading to loss of funds

OptimismPortal checks gas before spending 5122 gas on storage writes, so a griefer can finalize another user's withdrawal with just enough gas to pass the check but too little to execute, permanently locking funds.

- Payout: Contest reward
- Severity: high
- Program: Optimism
- Credited: obront
- Disclosed: 2023-01-19
- Write-up quality: 8/10
- Original: https://github.com/sherlock-audit/2023-01-optimism-judging/issues/109
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-01-optimism%3AH-1
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:high tech:evm tech:solidity technique:source-review vuln:business-logic vuln:dos

**Target:** OptimismPortal L1 withdrawal finalization contract
**Root cause:** The require(gasleft() >= gasLimit + FINALIZE_GAS_BUFFER) check runs ~5122 gas before the external call; the intervening l2Sender storage write (2900 gas) and call setup consume that buffer, so the target receives up to 5122 less than the user's gasLimit.
**Steps:**
1. User initiates withdrawal from L2ToL1MessagePasser with gasLimit G.
2. Attacker calls finalizeWithdrawalTransaction(_tx) supplying total gas such that gasleft() at the require is >= G + 20000 but < G + 20000 + 5122.
3. Require passes; l2Sender assignment and call prep consume ~5122 gas.
4. SafeCall.call forwards gasleft() - 20000, which is < G, so target execution reverts.
5. No replay: withdrawal is consumed and ETH stays locked in OptimismPortal.
**How found:** Read the code comment asserting the invariant that supplied gas >= user gasLimit, then noticed the check and the call are separated by a storage write and call setup; measured the 5122 gas delta with a Foundry PoC sweeping gas values to find the window where the require passes but execution fails.
**Exploitation:** Permissionless griefing: attacker front-runs or races any pending finalizeWithdrawalTransaction, supplying a precise gas amount (between the check threshold and the true requirement) so the check passes but the inner call OOGs. Because the portal has no replay, the withdrawal is permanently bricked and funds remain stuck.
**Insight:** When a contract asserts a gas invariant before an external call, count every op between the check and the call (SSTORE from non-zero, memory prep) — the buffer must cover them, not just the call itself. Sweep gas values in a PoC to find the exact gap window.
