In the world of Ethereum Virtual Machine (EVM) smart contract development, developers often assume that specifying a gas limit in a CALL instruction guarantees execution with at least that amount of gas. But as recent discoveries in Optimism Bedrock have shown, this assumption can be dangerously misleading.
This article dives into a subtle yet critical behavior of the EVM’s CALL opcode—commonly overlooked in documentation and audits—that can lead to transaction failures, locked funds, and irreversible losses in certain Layer 2 (L2) systems. We’ll explore how the 63/64 gas rule, introduced via EIP-150, interacts with real-world protocol logic to create unexpected edge cases, particularly in non-retryable cross-chain transactions.
Understanding Optimism’s Withdrawal Mechanism
Optimism uses a two-way messaging system for cross-layer communication between L1 (Ethereum) and L2 (Optimism). Two key transaction types enable this:
- Deposits: L1 → L2 transactions
- Withdrawals: L2 → L1 transactions
These withdrawals are non-retryable, meaning once executed—even if they fail—they cannot be retried. If a withdrawal includes ETH (msg.value > 0) and fails during execution on L1, those funds become permanently locked because the system prevents re-execution.
👉 Discover how secure blockchain interactions start with understanding low-level EVM mechanics.
How Withdrawals Are Finalized on L1
When a user initiates a withdrawal from L2, it eventually reaches L1 through the OptimismPortal.finalizeWithdrawalTransaction() function. Here's a simplified version of the critical steps:
- Check that the withdrawal hasn’t already been finalized.
Verify sufficient remaining gas:
require(gasleft() >= _tx.gasLimit + FINALIZE_GAS_BUFFER);Execute the target call using:
bool success = SafeCall.call( _tx.target, gasleft() - FINALIZE_GAS_BUFFER, _tx.value, _tx.data );
At first glance, this seems safe: ensure enough gas is left before making the call. However, this check alone is insufficient due to deeper EVM-level constraints.
The Sherlock Audit Bug: Gas Miscalculation Risk
During the Sherlock audit contest, a high-severity bug was reported (#109) where malicious actors could finalize someone else’s withdrawal with less gas than specified, causing failure and fund loss.
The root cause? Between the require check and the actual CALL, additional operations consume gas (e.g., SSTORE, memory allocation). This means the actual gas passed to CALL might fall below _tx.gasLimit.
A proposed fix increases the buffer:
require(gasleft() >= _tx.gasLimit + FINALIZE_GAS_BUFFER + 5_122);Adding a safety margin accounts for post-check gas usage.
But even this does not fully eliminate the risk—because of an obscure but fundamental EVM rule.
EIP-150 and the 63/64 Gas Rule
To prevent denial-of-service attacks via deep call stacks, EIP-150 ("Tangerine Whistle") introduced a crucial change: when executing CALL, STATICCALL, or DELEGATECALL, the effective gas limit is capped at 63/64 of the remaining gas.
This means:
- If you pass
gas = XtoCALL, the EVM will usemin(X, (gasleft * 63) / 64)as the actual gas available. - You cannot force a contract to receive more than ~98.4% of current
gasleft().
For example:
- Current
gasleft()= 1,000,000 - You specify
call(990_000, ...) - Actual gas provided =
floor(1,000,000 * 63 / 64)= 984,375
Even though your code says "use 990k", the EVM overrides it.
This behavior is not widely documented in high-level Solidity guides—and many developers assume their specified gas is respected.
Why This Matters for Optimism Withdrawals
Because withdrawals are non-retryable, any unexpected failure locks funds forever. Consider this scenario:
require(gasleft() >= _tx.gasLimit + 25_122); // Fixed buffer
...
SafeCall.call(target, gasleft() - 20_000, value, data);Suppose:
_tx.gasLimit = 1,368,975- Total gas supplied = 1,399,097 (just over required)
The check passes. But when SafeCall.call() runs:
gasleft()≈ 1,378,975_gasargument = ~1,358,975- However,
(gasleft * 63 / 64)≈ 1,342,000
So the actual gas given to the target contract is only ~1.34M—less than needed—causing failure.
Thus, even with a corrected gas buffer, the 63/64 rule can still cause under-gassing, leading to irreversible fund lockups.
Practical Example: Simulating the Failure
Using Foundry tests, we can reproduce this:
function testPortalBugWithEnoughGas() public {
_executePortalWithGivenGas(1_368_975 + 30_000);
vm.expectRevert(); gu.s(0); // Array empty → call failed
}Logs show:
gas left in the beginning: 1397920
gas provided to call: 1369583 , gasLeft: 1389336Despite passing over 1.36M gas to call, the internal EVM logic reduces it below the required threshold due to the 63/64 cap.
👉 Learn how advanced blockchain platforms handle gas-efficient contract design.
The Real Fix: Enforcing Minimum Effective Gas
Optimism recognized this issue and updated their SafeCall library with a new function: callWithMinGas.
Instead of just subtracting a fixed buffer, it ensures that:
effectiveGas = min(gasSupplied, (gasleft * 63) / 64)is still ≥ _tx.gasLimit.
This requires calculating the minimum pre-call gasleft() needed to satisfy both:
- Post-buffer gas availability
- The 63/64 rule constraint
Only by combining these checks can protocols guarantee users get their intended execution environment.
Core Keywords Identified
- EVM CALL opcode
- gas trap
- Optimism Bedrock
- 63/64 gas rule
- non-retryable transactions
- cross-chain withdrawals
- EIP-150
- fund locking
These terms reflect core concepts relevant to security audits, L2 design, and EVM-level development—critical for developers building secure interoperability layers.
Frequently Asked Questions (FAQ)
Q: What is the 63/64 gas rule in EVM?
The 63/64 rule limits the gas forwarded in a CALL operation to at most 63/64 of the remaining gas in the current context. Introduced in EIP-150, it prevents infinite call stack attacks by ensuring each nested call receives progressively less gas.
Q: Can I bypass the 63/64 gas cap?
No. This limit is enforced at the protocol level by all major EVM implementations (e.g., Geth). It cannot be avoided—even with assembly-level control over the call instruction.
Q: Why are non-retryable transactions risky?
If a transaction fails after consuming all its gas or due to under-gassing, and there's no mechanism to replay it, any attached value (like ETH) may be permanently lost. This makes precise gas estimation essential.
Q: How can developers protect against this trap?
Always account for both:
- Gas consumed between checks and calls
- The 63/64 reduction
Use helper functions likecallWithMinGasthat validate effective gas meets minimum requirements before proceeding.
Q: Does this affect other L2s besides Optimism?
Yes. Any system relying on finalizing L2-initiated calls on L1—with fixed or predictable gas limits—could face similar risks if they don’t consider EVM’s intrinsic gas capping behavior.
Q: Is this issue present in regular smart contracts?
In most standard contracts, temporary failure is acceptable and retries are possible. But in trust-minimized bridging systems, where finality is one-way and irreversible, such nuances become critical security concerns.
Conclusion
The discovery that "gas specified ≠ gas delivered" highlights a fundamental truth in blockchain development: low-level protocol details matter.
Even well-audited systems like Optimism Bedrock were vulnerable—not due to poor coding, but because subtle EVM behaviors like the 63/64 rule are poorly publicized yet have real financial consequences.
As developers build increasingly complex cross-chain applications, understanding these hidden mechanics becomes not just academic—it's essential for user safety.
👉 Stay ahead in secure blockchain development with up-to-date insights from leading platforms.