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. - 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).
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 gas remaining.
Halts execution with the given status.
Creates a new machine state for executing the given bytecode.
Fixes the trace cost of the opcode running now at the gas it has charged so
far, so later changes to the frame's gas do not count toward it. CALL
settles once it has charged the gas it forwards, and CREATE before it
forwards any, as geth's gasCost counts them (see EEVM.Tracer). Does
nothing when the state has no tracer.
Subtracts amount from the accumulated gas refund.
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{ 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 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 access-list pre-warming so sender, recipient, precompiles,
and from Shanghai (EIP-3651) 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):tx— transaction context (default: empty):block— block context (default: empty):hardfork— hardfork spec id (e.g.:cancun,:berlin; default::cancun):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
Fixes the trace cost of the opcode running now at the gas it has charged so
far, so later changes to the frame's gas do not count toward it. CALL
settles once it has charged the gas it forwards, and CREATE before it
forwards any, as geth's gasCost counts them (see EEVM.Tracer). Does
nothing when the state has no tracer.
@spec sub_refund(t(), non_neg_integer()) :: t()
Subtracts amount from the accumulated gas refund.
The counter is frame-local (a child frame starts at 0 and its counter is added to the parent's on success), so it may go negative: a child can undo a refund that its parent earned, e.g. by restoring a slot the parent cleared.
@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.