Discussion topic for the CALLSTATUS draft EIP: Add EIP: CALLSTATUS instruction by svlachakis · Pull Request #12586 · ethereum/EIPs · GitHub
Update Log
- 2026-10-25: initial draft, Add EIP: CALLSTATUS instruction by svlachakis · Pull Request #12586 · ethereum/EIPs · GitHub
- 2026-10-25: opcode moved from 0x4D to 0xA5, because 0x4D is proposed in #12577
External Reviews
None as of 2026-10-26.
Outstanding Issues
- 2026-10-26: Opcode number. 0xA5 is free as far as I can tell; 0x4B to 0x4F all have open proposals.
- 2026-10-26: Whether to expose 4 (success with a halt below), or let the flag affect only reverts.
- 2026-10-26: Prototype implementation and execution-spec tests for the 58 listed test cases.
- 2026-10-26: Compiler surface, for example Solidity try/catch exposing the halt bit.
- 2026-10-26: EIP-8141 frame transactions exposing the same codes for completed frames via FRAMEPARAM.
What
A new instruction, CALLSTATUS (tentatively 0xA5, 2 gas), that pushes the completion status of the most recent CALL / CALLCODE / DELEGATECALL / STATICCALL / CREATE / CREATE2 of the current frame, as three bits:
- bit 0 (1): the operation reverted, or was not started and returned its gas
- bit 1 (2): the operation halted exceptionally, or failed consuming the gas it would have forwarded
- bit 2 (4): an operation in a frame below halted exceptionally or collided
Values that occur: 0 success, 1 revert, 2 halt, 4 success with a halt below, 5 revert with a halt below. The invariant: status >= 2 iff gas forwarded somewhere in the line of the call was consumed by an exceptional halt or an address collision. Codes 0/1/2 are the EIP-7069 ones.
Why
Contracts that continue after a failed call must tell an out-of-gas failure, which the transaction sender can cause by choosing the gas, from a deliberate revert, which can be recorded as final (insufficient gas griefing). Today the only evidence is gasleft() arithmetic after the call, and every repricing fork has broken such thresholds. The two-dimensional gas of EIP-8037, with a reservoir that GAS does not show and refills on clears and reverts, weakens that evidence further. The EVM knows the outcome exactly at every frame boundary. This instruction exposes it, so a contract can treat status >= 2 as “not final” and stop depending on the gas schedule.
Intermediaries
Most calls go through intermediaries: proxies, wrappers that revert with a reason (OpenZeppelin Address.functionCall), batches that let a call fail and continue (Multicall3), and checks that return false after a failed call (ERC-1271 signature checkers). If the status reported only the direct callee’s own ending, all of these would turn a halt into “deliberate revert”. So each frame keeps a sticky halt_seen flag, set when any of its calls ends with status >= 2, and the flag travels up through every frame completion, revert or return. A sender who controls only the gas can therefore never make a caller see 1 for a failure whose line contains a halt.
The price: some deliberate failures carry the bit too (a low-gas probe that halts, followed by an unrelated revert, reads as 5). That is the safe direction, and the revert data is still there.
What it does not do
- It reports outcomes, not causes: a callee can produce 2 on purpose with INVALID, as it can today.
- A revert that depends on gasleft() without a halt stays under the sender’s control. Gas guards that want to be reported as halts should INVALID, not revert.
- It does not say whether a fixed gas budget was adequate under the current schedule.
- It protects code written after it ships; deployed contracts do not read it.