The EVM machine state — the envelope that holds everything that survives a single opcode step.
Anatomy
- frame (
Frame.t()) — the active per-call execution context: pc, stack, memory, gas, refund, code, return_data, contract, depth, is_static, parent return write-back location. - env (
Env.t()) — read-only execution environment for this call: tx, block, hardfork config. - substate (
Substate.t()) — transaction-scoped accumulators: touched / accessed / created / original / transient / logs. - db — unified world-state backend (accounts + storage).
- call_stack —
[Frame.t()]of suspended parent frames. status —
:running | :stopped | :reverted | :invalid | :out_of_gasor{:error, atom()}.- tracer — optional opcode-level trace recorder.
Frame, env, and substate isolate the three lifecycles cleanly: frame is per-call mutable, env is per-call immutable, substate is transaction-scoped.
Summary
Functions
Adds amount to the accumulated gas refund.
Advances the program counter by n positions.
Deducts gas from the active frame.
Returns the opcode byte at the current program counter, or nil if past end.
Returns the gas remaining.
Halts execution with the given status.
Creates a new machine state for executing the given bytecode.
Reads n bytes from the code starting at offset.
Subtracts amount from refund, flooring at 0.
Marks an address as touched for EIP-161 post-transaction cleanup.
Replaces the active frame using the given function.
Types
@type status() :: :running | :stopped | :reverted | :invalid | :out_of_gas | {:error, atom()}
@type t() :: %EEVM.Interpreter.MachineState{ call_stack: [EEVM.Interpreter.MachineState.Frame.t()], db: EEVM.Database.t(), env: EEVM.Interpreter.MachineState.Env.t(), frame: EEVM.Interpreter.MachineState.Frame.t(), status: status(), substate: EEVM.Interpreter.MachineState.Substate.t(), tracer: EEVM.Tracer.t() | nil }
Functions
@spec add_refund(t(), non_neg_integer()) :: t()
Adds amount to the accumulated gas refund.
@spec advance_pc(t(), non_neg_integer()) :: t()
Advances the program counter by n positions.
@spec consume_gas(t(), non_neg_integer()) :: {:ok, t()} | {:error, :out_of_gas, t()}
Deducts gas from the active frame.
Returns {:ok, updated_state} if sufficient gas remains, or
{:error, :out_of_gas, state} if the gas would go negative.
@spec current_depth(t()) :: non_neg_integer()
@spec current_opcode(t()) :: non_neg_integer() | nil
Returns the opcode byte at the current program counter, or nil if past end.
@spec gas_remaining(t()) :: non_neg_integer()
Returns the gas remaining.
Halts execution with the given status.
:stopped and :reverted keep the frame's remaining gas. Exceptional halts
(:invalid, :out_of_gas, {:error, reason}) consume all of it and
produce no return data.
Creates a new machine state for executing the given bytecode.
Includes EIP-2929/EIP-3651 access-list pre-warming so sender, recipient,
precompiles, and block.coinbase start warm for address-access gas metering.
Parameters
code— the raw EVM bytecode as an Elixir binaryopts— optional keyword list::gas— initial gas (default: 1,000,000):db— unified external database (default: empty in-memory DB):storage— legacy initial storage (default: empty, backward-compat):tx— transaction context (default: empty):block— block context (default: empty):hardfork— hardfork spec id (e.g.:cancun,:berlin; default::cancun):world_state— legacy external account state (default: empty, backward-compat):call_stack— internal frame stack (default:[]):frame_return_offset— parent memory write-back offset (default:0):frame_return_size— parent memory write-back size (default:0):is_static— static context flag for this frame (default:false):depth— current call depth (default:0):refund— gas refund counter (default:0):tracer— optionalEEVM.Tracerto record per-opcode trace (default:nil)
Example
iex> state = EEVM.Interpreter.MachineState.new(<<0x60, 0x01, 0x60, 0x02, 0x01>>)
iex> state.frame.pc
0
@spec push_frame(t(), EEVM.Interpreter.MachineState.Frame.t()) :: {:ok, t()} | {:error, :max_call_depth, t()}
@spec read_code(t(), non_neg_integer(), non_neg_integer()) :: binary()
Reads n bytes from the code starting at offset.
Used by PUSH instructions to read their immediate data.
@spec sub_refund(t(), non_neg_integer()) :: t()
Subtracts amount from refund, flooring at 0.
@spec touch_address(t(), non_neg_integer()) :: t()
Marks an address as touched for EIP-161 post-transaction cleanup.
@spec update_frame(t(), (EEVM.Interpreter.MachineState.Frame.t() -> EEVM.Interpreter.MachineState.Frame.t())) :: t()
Replaces the active frame using the given function.
Convenience for callers that want to update one or more per-frame fields without re-spelling the nested struct update.