EIP draft: CALLSTATUS instruction

Discussion topic for the CALLSTATUS draft EIP: Add EIP: CALLSTATUS instruction by svlachakis · Pull Request #12586 · ethereum/EIPs · GitHub

Update Log

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.
1 Like