The EVM interpreter — fetches, decodes, and dispatches opcodes in a loop.
This is the core opcode-execution engine. It is invoked by EEVM.Handler
(the transaction-level pipeline) and by system contracts that need to
evaluate bytecode against an existing world state.
Execution ends when a terminating opcode is reached (STOP, RETURN, REVERT, INVALID), the program counter advances past the end of the bytecode (implicit STOP), or gas runs out before the opcode can execute. INVALID (0xFE) is special-cased: its "base" gas cost is set to the entire remaining gas, draining it completely before the opcode runs.
Opcode dispatch is data-driven via the frame's per-hardfork
EEVM.Interpreter.InstructionTable (built from
EEVM.Interpreter.Instructions.Registry); opcode modules own their
semantics, and the interpreter only orchestrates.
Summary
Functions
Applies the transaction-end steps to a halted top-level state: deletes the
accounts that SELFDESTRUCTed and touched empty accounts (EIP-161), then settles the refund counter into
frame.gas and resets the counter to 0.
Creates a MachineState from bytecode and options, then runs the execution loop.
The main execution loop. Runs the active frame until it halts.
Functions
@spec finalize( EEVM.Interpreter.MachineState.t(), non_neg_integer(), non_neg_integer() ) :: EEVM.Interpreter.MachineState.t()
Applies the transaction-end steps to a halted top-level state: deletes the
accounts that SELFDESTRUCTed and touched empty accounts (EIP-161), then settles the refund counter into
frame.gas and resets the counter to 0.
The refund is capped at a fraction of the gas used by the whole
transaction: 1/5 from London (EIP-3529), 1/2 before. When env.tx.gas_limit
is set, gas used counts from it and so includes intrinsic gas; otherwise it
counts from initial_gas. The execution refund counts only when execution
succeeded; authorization_refund (EIP-7702) always counts. From Prague the
gas charged after the refund is floored at the EIP-7623 calldata cost.
run/2 calls this itself. Callers that charge more gas after execution
ends, such as the code deposit of a CREATE transaction, call run_loop/1,
charge that gas, then call this, so the cap counts it.
@spec run( binary(), keyword() ) :: EEVM.Interpreter.MachineState.t()
Creates a MachineState from bytecode and options, then runs the execution loop.
Options
:gas— initial gas limit (default: 1_000_000):value— ETH value sent with the call, in wei:code— contract bytecode (usually the same as the first argument):calldata— ABI-encoded input data for the call:caller— address of the account initiating this call:origin— address of the original transaction signer:authorization_refund— EIP-7702 refund earned before execution; it is added to the refund counter even when execution fails
Returns the final MachineState after execution completes, with the
transaction-end steps of finalize/3 applied.
@spec run_loop(EEVM.Interpreter.MachineState.t()) :: EEVM.Interpreter.MachineState.t()
The main execution loop. Runs the active frame until it halts.
Any status but :running means the frame has already halted, and the state
is returned as-is. It runs one frame: CALL and CREATE run each child frame in
a nested call and merge the result into the parent
(EEVM.Interpreter.Journal). It is public for those callers and the
transaction handler; it is not part of the public API — use run/2.