Skip to main content

Overview

The Host module provides an interface for EVM opcodes to access external blockchain state. It abstracts account storage, balances, code, and nonces behind a unified API that instruction handlers call during execution. This is the boundary between the EVM execution engine and the state layer - whether that’s an in-memory mock, a database, or a live Ethereum node.

Architecture Note

This module provides low-level EVM primitives (opcode handlers, frame management). For full EVM execution with nested calls, use:
  • guillotine: Production EVM with async state access, tracing, and full EIP support
  • guillotine-mini: Lightweight synchronous EVM for testing and simple use cases
The optional call and create methods enable nested execution. When not provided, system opcodes (CALL, CREATE, etc.) return NotImplemented error.

Type Definition

type BrandedHost = {
  readonly [brand]: "Host";

  // Account state
  getBalance: (address: Address) => bigint;
  setBalance: (address: Address, balance: bigint) => void;
  getCode: (address: Address) => Uint8Array;
  setCode: (address: Address, code: Uint8Array) => void;
  getNonce: (address: Address) => bigint;
  setNonce: (address: Address, nonce: bigint) => void;

  // Persistent storage
  getStorage: (address: Address, slot: bigint) => bigint;
  setStorage: (address: Address, slot: bigint, value: bigint) => void;

  // Transient storage (EIP-1153)
  getTransientStorage: (address: Address, slot: bigint) => bigint;
  setTransientStorage: (address: Address, slot: bigint, value: bigint) => void;

  // Nested execution (optional)
  call?: (params: CallParams) => CallResult;
  create?: (params: CreateParams) => CreateResult;
};

API

Host(impl)

Create a Host from an implementation object.
import { Host } from 'voltaire/evm/Host';

const host = Host({
  getBalance: (addr) => balances.get(toKey(addr)) ?? 0n,
  setBalance: (addr, bal) => balances.set(toKey(addr), bal),
  getCode: (addr) => codes.get(toKey(addr)) ?? new Uint8Array(0),
  setCode: (addr, code) => codes.set(toKey(addr), code),
  getStorage: (addr, slot) => storage.get(`${toKey(addr)}-${slot}`) ?? 0n,
  setStorage: (addr, slot, val) => storage.set(`${toKey(addr)}-${slot}`, val),
  getNonce: (addr) => nonces.get(toKey(addr)) ?? 0n,
  setNonce: (addr, nonce) => nonces.set(toKey(addr), nonce),
  getTransientStorage: (addr, slot) => transient.get(`${toKey(addr)}-${slot}`) ?? 0n,
  setTransientStorage: (addr, slot, val) => transient.set(`${toKey(addr)}-${slot}`, val),
});

Host.from(impl)

Alias for Host(impl). Creates a branded Host from implementation.

Host.createMemoryHost()

Create an in-memory Host for testing. All state stored in Maps.
import { Host } from 'voltaire/evm/Host';
import * as Address from 'voltaire/primitives/Address';

const host = Host.createMemoryHost();
const addr = Address.from("0x1234567890123456789012345678901234567890");

// Set and get balance
host.setBalance(addr, 1000000000000000000n); // 1 ETH
console.log(host.getBalance(addr)); // 1000000000000000000n

// Set and get storage
host.setStorage(addr, 0x42n, 0x1337n);
console.log(host.getStorage(addr, 0x42n)); // 0x1337n

// Set and get code
host.setCode(addr, new Uint8Array([0x60, 0x80, 0x60, 0x40]));
console.log(host.getCode(addr).length); // 4

Methods

Account State

MethodDescriptionUsed By
getBalance(address)Get ETH balance in weiBALANCE (0x31)
setBalance(address, balance)Set ETH balanceTransaction processing
getCode(address)Get contract bytecodeEXTCODESIZE, EXTCODECOPY, EXTCODEHASH
setCode(address, code)Deploy contract codeCREATE, CREATE2
getNonce(address)Get account nonceTransaction validation
setNonce(address, nonce)Increment nonceTransaction processing

Persistent Storage

MethodDescriptionUsed By
getStorage(address, slot)Read storage slotSLOAD (0x54)
setStorage(address, slot, value)Write storage slotSSTORE (0x55)

Transient Storage (EIP-1153)

Transaction-scoped storage cleared at end of transaction.
MethodDescriptionUsed By
getTransientStorage(address, slot)Read transient slotTLOAD (0x5c)
setTransientStorage(address, slot, value)Write transient slotTSTORE (0x5d)

Nested Execution (Optional)

MethodDescriptionUsed By
call(params)Execute nested callCALL, STATICCALL, DELEGATECALL, CALLCODE
create(params)Deploy new contractCREATE, CREATE2

Examples

Custom Host with Logging

import { Host } from 'voltaire/evm/Host';
import * as Hex from 'voltaire/primitives/Hex';

const logs: string[] = [];

const host = Host.from({
  getBalance: (addr) => {
    logs.push(`getBalance: ${Hex.fromBytes(addr)}`);
    return 0n;
  },
  setBalance: (addr, balance) => {
    logs.push(`setBalance: ${Hex.fromBytes(addr)} = ${balance}`);
  },
  getCode: (addr) => {
    logs.push(`getCode: ${Hex.fromBytes(addr)}`);
    return new Uint8Array(0);
  },
  setCode: (addr, code) => {
    logs.push(`setCode: ${Hex.fromBytes(addr)} (${code.length} bytes)`);
  },
  getStorage: (addr, slot) => {
    logs.push(`getStorage: ${Hex.fromBytes(addr)} [${slot}]`);
    return 0n;
  },
  setStorage: (addr, slot, value) => {
    logs.push(`setStorage: ${Hex.fromBytes(addr)} [${slot}] = ${value}`);
  },
  getNonce: (addr) => {
    logs.push(`getNonce: ${Hex.fromBytes(addr)}`);
    return 0n;
  },
  setNonce: (addr, nonce) => {
    logs.push(`setNonce: ${Hex.fromBytes(addr)} = ${nonce}`);
  },
  getTransientStorage: () => 0n,
  setTransientStorage: () => {},
});

Using with SLOAD Instruction

import { Host } from 'voltaire/evm/Host';
import { Frame } from 'voltaire/evm/Frame';
import { sload } from 'voltaire/evm/instructions/storage';
import * as Address from 'voltaire/primitives/Address';

const host = Host.createMemoryHost();
const addr = Address.from("0x1234567890123456789012345678901234567890");

// Pre-populate storage
host.setStorage(addr, 0x42n, 0x1337n);

// Create execution frame
const frame = Frame({
  code: new Uint8Array([0x54]), // SLOAD opcode
  address: addr,
  gasRemaining: 10000n,
});

// Push key onto stack
Frame.pushStack(frame, 0x42n);

// Execute SLOAD - reads from host
const error = sload(frame, host);

console.log(Frame.popStack(frame).value); // 0x1337n

Multiple Account Management

const host = Host.createMemoryHost();

const alice = Address.from("0x1111111111111111111111111111111111111111");
const bob = Address.from("0x2222222222222222222222222222222222222222");
const contract = Address.from("0x3333333333333333333333333333333333333333");

// Set balances
host.setBalance(alice, 10n * 10n**18n); // 10 ETH
host.setBalance(bob, 5n * 10n**18n);    // 5 ETH

// Set nonces
host.setNonce(alice, 5n);
host.setNonce(bob, 12n);

// Deploy contract code
host.setCode(contract, new Uint8Array([
  0x60, 0x80, // PUSH1 0x80
  0x60, 0x40, // PUSH1 0x40
  0x52,       // MSTORE
]));

// Set contract storage
host.setStorage(contract, 0n, 111n);
host.setStorage(contract, 1n, 222n);

// All state isolated by address
console.log(host.getBalance(alice)); // 10000000000000000000n
console.log(host.getBalance(bob));   // 5000000000000000000n
console.log(host.getCode(alice).length); // 0 (EOA, no code)
console.log(host.getCode(contract).length); // 5 (contract)

Transient Storage (EIP-1153)

Transient storage provides transaction-scoped data that is:
  • Isolated per contract address
  • Cleared at end of transaction
  • Cheaper than persistent storage (no disk writes)
  • Useful for reentrancy locks, callbacks, flash loans
const host = Host.createMemoryHost();
const addr = Address.from("0x1234567890123456789012345678901234567890");

// Set transient value
host.setTransientStorage(addr, 0x99n, 0xABCDn);

// Read transient value
console.log(host.getTransientStorage(addr, 0x99n)); // 0xABCDn

// Transient and persistent storage are independent
host.setStorage(addr, 0x99n, 0x1111n);
console.log(host.getTransientStorage(addr, 0x99n)); // 0xABCDn (unchanged)
console.log(host.getStorage(addr, 0x99n)); // 0x1111n

Edge Cases

Uninitialized Values

const host = Host.createMemoryHost();
const addr = Address.from("0x1234567890123456789012345678901234567890");

// All uninitialized values return zero/empty
console.log(host.getBalance(addr)); // 0n
console.log(host.getNonce(addr)); // 0n
console.log(host.getStorage(addr, 0x42n)); // 0n
console.log(host.getCode(addr)); // Uint8Array(0)

Max Uint256 Values

const MAX = (1n << 256n) - 1n;

host.setBalance(addr, MAX);
host.setStorage(addr, MAX, MAX); // Max slot, max value

console.log(host.getBalance(addr)); // MAX
console.log(host.getStorage(addr, MAX)); // MAX

Large Code (EIP-170: 24KB limit)

const largeCode = new Uint8Array(24576); // 24KB max
host.setCode(addr, largeCode);
console.log(host.getCode(addr).length); // 24576

References