Documentation Index
Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
Overview
A Frame represents a single EVM execution context. When a smart contract executes, it runs within a frame that maintains:
- Stack - 1024-element LIFO stack of 256-bit values
- Memory - Sparse byte-addressable scratch space
- Gas - Remaining gas for execution
- Call context - Caller, address, value, calldata
Each CALL, DELEGATECALL, STATICCALL, CREATE, or CREATE2 creates a new nested frame. The EVM supports up to 1024 call depth.
Type Definition
import type { BrandedFrame } from 'voltaire/evm/Frame';
// BrandedFrame structure
type BrandedFrame = {
// Stack (max 1024 items)
stack: bigint[];
// Memory (sparse map)
memory: Map<number, number>;
memorySize: number; // Word-aligned size
// Execution state
pc: number; // Program counter
gasRemaining: bigint;
bytecode: Uint8Array;
// Call context
caller: Address;
address: Address;
value: bigint;
calldata: Uint8Array;
output: Uint8Array;
returnData: Uint8Array;
// Flags
stopped: boolean;
reverted: boolean;
isStatic: boolean;
// Other
authorized: bigint | null;
callDepth: number;
// Optional: hardfork, access lists, block context, logs...
};
Creating a Frame
import { Frame } from 'voltaire/evm/Frame';
// Create with defaults
const frame = Frame();
// Create with parameters
const frame = Frame({
bytecode: new Uint8Array([0x60, 0x01, 0x60, 0x02, 0x01]), // PUSH1 1, PUSH1 2, ADD
gas: 100000n,
caller: callerAddress,
address: contractAddress,
value: 0n,
calldata: new Uint8Array([]),
isStatic: false,
});
Frame Parameters
| Parameter | Type | Default | Description |
|---|
bytecode | Uint8Array | [] | Contract bytecode to execute |
gas | bigint | 1000000n | Initial gas allocation |
caller | Address | zero address | Message sender (msg.sender) |
address | Address | zero address | Current contract address |
value | bigint | 0n | Wei transferred (msg.value) |
calldata | Uint8Array | [] | Input data (msg.data) |
isStatic | boolean | false | Static call (no state modifications) |
stack | bigint[] | [] | Initial stack state |
gasRemaining | bigint | gas | Override gas (for resuming) |
Stack Operations
pushStack
Push a 256-bit value onto the stack.
import { Frame, pushStack } from 'voltaire/evm/Frame';
const frame = Frame();
const error = pushStack(frame, 42n);
if (error) {
// error.type === "StackOverflow" when stack has 1024 items
console.error(error.type);
}
popStack
Pop the top value from the stack.
import { Frame, popStack, pushStack } from 'voltaire/evm/Frame';
const frame = Frame();
pushStack(frame, 100n);
pushStack(frame, 200n);
const result = popStack(frame);
if (result.error) {
// result.error.type === "StackUnderflow" when stack is empty
console.error(result.error.type);
} else {
console.log(result.value); // 200n (LIFO)
}
peekStack
Read a stack value without removing it.
import { Frame, peekStack, pushStack } from 'voltaire/evm/Frame';
const frame = Frame();
pushStack(frame, 10n);
pushStack(frame, 20n);
pushStack(frame, 30n);
const top = peekStack(frame, 0); // 30n (top)
const second = peekStack(frame, 1); // 20n
const third = peekStack(frame, 2); // 10n
Gas Operations
consumeGas
Deduct gas from the frame. Returns OutOfGas error if insufficient.
import { Frame, consumeGas } from 'voltaire/evm/Frame';
const frame = Frame({ gas: 100n });
consumeGas(frame, 30n); // gasRemaining: 70n
consumeGas(frame, 50n); // gasRemaining: 20n
const error = consumeGas(frame, 50n);
if (error) {
console.log(error.type); // "OutOfGas"
console.log(frame.gasRemaining); // 0n
}
Memory Operations
writeMemory
Write a byte to memory. Memory expands in 32-byte words.
import { Frame, writeMemory } from 'voltaire/evm/Frame';
const frame = Frame();
writeMemory(frame, 0, 0xde);
writeMemory(frame, 1, 0xad);
writeMemory(frame, 2, 0xbe);
writeMemory(frame, 3, 0xef);
console.log(frame.memorySize); // 32 (word-aligned)
readMemory
Read a byte from memory. Uninitialized memory returns 0.
import { Frame, readMemory, writeMemory } from 'voltaire/evm/Frame';
const frame = Frame();
writeMemory(frame, 100, 0xff);
console.log(readMemory(frame, 100)); // 255 (0xff)
console.log(readMemory(frame, 0)); // 0 (uninitialized)
memoryExpansionCost
Calculate gas cost for memory expansion.
import { Frame, memoryExpansionCost, consumeGas, writeMemory } from 'voltaire/evm/Frame';
const frame = Frame({ gas: 1000n });
// Calculate cost before expanding
const cost = memoryExpansionCost(frame, 64);
consumeGas(frame, cost);
// Now safe to write
writeMemory(frame, 63, 0xff);
Memory cost formula: 3n + n²/512 where n is word count. This quadratic growth prevents memory DoS attacks.
Error Types
type EvmError =
| { type: "StackOverflow" } // Stack exceeds 1024 items
| { type: "StackUnderflow" } // Pop/peek from empty stack
| { type: "OutOfGas" } // Insufficient gas
| { type: "OutOfBounds" } // Invalid memory access
| { type: "InvalidJump" } // Jump to non-JUMPDEST
| { type: "InvalidOpcode" } // Unknown opcode
| { type: "RevertExecuted" } // REVERT opcode executed
| { type: "CallDepthExceeded" } // Call depth > 1024
| { type: "WriteProtection" } // State modification in STATICCALL
| { type: "InsufficientBalance" }
| { type: "NotImplemented"; message: string };
Arithmetic Methods
Frames include bound arithmetic methods for opcodes 0x01-0x0b:
const frame = Frame();
pushStack(frame, 10n);
pushStack(frame, 20n);
const error = frame.add(); // Stack: [30n]
// Available methods:
// frame.add(), frame.mul(), frame.sub(), frame.div(), frame.sdiv()
// frame.mod(), frame.smod(), frame.addmod(), frame.mulmod()
// frame.exp(), frame.signextend()
Complete Example
import { Frame, pushStack, popStack, consumeGas, writeMemory, readMemory } from 'voltaire/evm/Frame';
import { Address } from 'voltaire/primitives/Address';
// Simulate ADD opcode execution
const frame = Frame({
bytecode: new Uint8Array([0x60, 0x0a, 0x60, 0x14, 0x01]), // PUSH1 10, PUSH1 20, ADD
gas: 21000n,
caller: Address("0x1111111111111111111111111111111111111111"),
address: Address("0x2222222222222222222222222222222222222222"),
});
// PUSH1 10 (gas: 3)
consumeGas(frame, 3n);
pushStack(frame, 10n);
frame.pc += 2;
// PUSH1 20 (gas: 3)
consumeGas(frame, 3n);
pushStack(frame, 20n);
frame.pc += 2;
// ADD (gas: 3)
consumeGas(frame, 3n);
const error = frame.add();
frame.pc += 1;
// Result
const result = popStack(frame);
console.log(result.value); // 30n
console.log(frame.gasRemaining); // 20991n
API Reference
| Function | Description |
|---|
Frame(params?) | Create new execution frame |
pushStack(frame, value) | Push bigint onto stack |
popStack(frame) | Pop and return top value |
peekStack(frame, index) | Read value at depth index |
consumeGas(frame, amount) | Deduct gas from frame |
readMemory(frame, offset) | Read byte from memory |
writeMemory(frame, offset, value) | Write byte to memory |
memoryExpansionCost(frame, endBytes) | Calculate expansion gas |