# `EEVM.Interpreter.MachineState`
[🔗](https://github.com/mw2000/eevm/blob/main/lib/eevm/interpreter/machine_state.ex#L1)

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_gas`
  or `{: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.

# `status`

```elixir
@type status() ::
  :running | :stopped | :reverted | :invalid | :out_of_gas | {:error, atom()}
```

# `t`

```elixir
@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
}
```

# `add_refund`

```elixir
@spec add_refund(t(), non_neg_integer()) :: t()
```

Adds `amount` to the accumulated gas refund.

# `advance_pc`

```elixir
@spec advance_pc(t(), non_neg_integer()) :: t()
```

Advances the program counter by `n` positions.

# `consume_gas`

```elixir
@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.

# `current_depth`

```elixir
@spec current_depth(t()) :: non_neg_integer()
```

# `current_opcode`

```elixir
@spec current_opcode(t()) :: non_neg_integer() | nil
```

Returns the opcode byte at the current program counter, or nil if past end.

# `gas_remaining`

```elixir
@spec gas_remaining(t()) :: non_neg_integer()
```

Returns the gas remaining.

# `halt`

```elixir
@spec halt(t(), status()) :: t()
```

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.

# `new`

```elixir
@spec new(
  binary(),
  keyword()
) :: t()
```

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 binary
  - `opts` — 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` — optional `EEVM.Tracer` to record per-opcode trace (default: `nil`)

## Example

    iex> state = EEVM.Interpreter.MachineState.new(<<0x60, 0x01, 0x60, 0x02, 0x01>>)
    iex> state.frame.pc
    0

# `pop_frame`

```elixir
@spec pop_frame(t()) :: {:ok, t()} | {:error, :empty_call_stack, t()}
```

# `push_frame`

```elixir
@spec push_frame(t(), EEVM.Interpreter.MachineState.Frame.t()) ::
  {:ok, t()} | {:error, :max_call_depth, t()}
```

# `read_code`

```elixir
@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.

# `sub_refund`

```elixir
@spec sub_refund(t(), non_neg_integer()) :: t()
```

Subtracts `amount` from refund, flooring at 0.

# `touch_address`

```elixir
@spec touch_address(t(), non_neg_integer()) :: t()
```

Marks an address as touched for EIP-161 post-transaction cleanup.

# `update_frame`

```elixir
@spec update_frame(t(), (EEVM.Interpreter.MachineState.Frame.t() -&gt;
                     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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
