Skip to content

Guillotine should have a useful examples/ folder #25

@roninjin10

Description

@roninjin10

Objective

Create a comprehensive examples/ folder demonstrating how to use guillotine-mini's EVM implementation. Examples should showcase different API patterns, call types, and execution modes to help users integrate the library.

Essential Examples

0. build - Build.zig

Before getting started ask an llm to add a new executable target to a examples/main.zig. It should be a simple cli app that takes an example name and executes that example.

1. simple-call - Basic EVM execution

  • Initialize EVM with basic state
  • Execute simple bytecode (e.g., arithmetic ops, storage read/write)
  • Show CallResult handling (success/failure, gas used, output)
  • Demonstrate balance transfers

2. access-list - EIP-2929 warm/cold access

  • Create transaction with access list (EIP-2930)
  • Show gas savings from pre-warming addresses/storage slots
  • Compare costs: cold vs warm SLOAD/SSTORE
  • Demonstrate evm.accessAddress() / evm.accessStorageSlot()

3. call-types - Different call operations

Show all call variants with CallParams:

  • CALL: Regular external call with value transfer
  • STATICCALL: Read-only call (no state changes)
  • DELEGATECALL: Execute code in caller's context
  • CALLCODE: Legacy delegatecall variant
  • CREATE: Deploy new contract
  • CREATE2: Deterministic deployment with salt

4. direct-frame - Low-level Frame execution

  • Initialize Frame directly (bypass Evm orchestrator)
  • Execute bytecode step-by-step with frame.step()
  • Inspect stack/memory/PC after each instruction
  • Useful for debugging or custom interpreters

5. nested-calls - Call stack and reentrancy

  • Contract A calls Contract B calls Contract C
  • Show call depth tracking (max 1024)
  • Demonstrate context switching (caller/address/value)
  • Handle failures at different depths

6. precompiles - Using precompiled contracts

  • Call precompiles (0x01-0x09: ecrecover, SHA-256, RIPEMD-160, identity, modexp, etc.)
  • Show gas costs and input/output handling
  • Demonstrate integration with regular calls

7. tracing - EIP-3155 trace capture

  • Enable tracer on Evm
  • Execute bytecode with full trace
  • Export trace in EIP-3155 JSON format
  • Compare with geth/execution-specs traces

8. transient-storage - EIP-1153 (Cancun+)

  • Use TLOAD/TSTORE for temporary state
  • Show transaction-scoped lifecycle (cleared at tx end)
  • Demonstrate reentrancy guard pattern
  • Compare gas costs vs persistent SSTORE

9. hardfork-features - Fork-specific behavior

  • Run same bytecode across hardforks (Berlin, London, Shanghai, Cancun)
  • Show gas cost differences (EIP-2929, EIP-3529)
  • Demonstrate new opcodes (PUSH0, MCOPY, TSTORE)
  • Validate EIP activation logic

10. host-interface - Custom state backend

  • Implement custom Host for external state (DB, in-memory, etc.)
  • Show vtable setup for getBalance/setBalance/getCode/etc.
  • Demonstrate state isolation between transactions

11. CI - github actions

examples should be added to a github actions job that runs each example and just makes sure they exit 0

API Patterns to Demonstrate

CallResult Handling

// Success cases
const result = try CallResult.success_with_output(allocator, gas_left, output);
const empty = try CallResult.success_empty(allocator, gas_left);
const with_logs = try CallResult.success_with_logs(allocator, gas_left, output, logs);

// Failure cases
const failed = try CallResult.failure(allocator, gas_left);
const reverted = try CallResult.revert_with_data(allocator, gas_left, revert_data);
const error_result = try CallResult.failure_with_error(allocator, gas_left, error_msg);

// Inspection
if (result.isSuccess()) {
    const gas_used = result.gasConsumed(original_gas);
    // Process output
}

CallParams Construction

// Regular call
const call_params = CallParams{ .call = .{
    .caller = alice,
    .to = contract,
    .value = 1000,
    .input = calldata,
    .gas = 100000,
}};

// Staticcall (read-only)
const static_params = CallParams{ .staticcall = .{
    .caller = alice,
    .to = contract,
    .input = calldata,
    .gas = 100000,
}};

// CREATE2 (deterministic deployment)
const create2_params = CallParams{ .create2 = .{
    .caller = alice,
    .value = 0,
    .init_code = bytecode,
    .salt = 0x1234,
    .gas = 200000,
}};

// Validation
try call_params.validate();

Direct Frame Execution

// Initialize frame
var frame = try Frame.init(
    allocator,
    bytecode,
    gas,
    caller,
    address,
    value,
    calldata,
    evm_ptr,
    hardfork,
    is_static,
);
defer frame.deinit();

// Execute all at once
try frame.execute();

// Or step-by-step for debugging
while (!frame.stopped and !frame.reverted) {
    try frame.step(); // Execute one opcode
    // Inspect: frame.stack, frame.memory, frame.pc, frame.gas_remaining
}

Structure

examples/
├── README.md                    # Overview, build instructions
├── build.zig                    # Shared build config
├── simple-call/
│   ├── main.zig                # Minimal working example
│   └── README.md
├── access-list/
│   ├── main.zig
│   └── README.md
├── call-types/
│   ├── main.zig
│   └── README.md
├── direct-frame/
│   ├── main.zig
│   └── README.md
├── nested-calls/
│   ├── main.zig
│   ├── contracts/             # Bytecode for contract A, B, C
│   └── README.md
├── precompiles/
│   ├── main.zig
│   └── README.md
├── tracing/
│   ├── main.zig
│   ├── trace-output.json      # Sample trace
│   └── README.md
├── transient-storage/
│   ├── main.zig
│   └── README.md
├── hardfork-features/
│   ├── main.zig
│   └── README.md
└── host-interface/
    ├── main.zig
    ├── custom-host.zig        # Example Host implementation
    └── README.md

Implementation Notes

  • Each example should be self-contained and runnable via zig build run-<example>
  • Include inline comments explaining each step
  • Show error handling patterns (OutOfGas, StackUnderflow, etc.)
  • Provide expected output in README
  • Use real bytecode (from solc or hand-assembled) where practical
  • Keep examples minimal but complete (no mock TODOs)

Acceptance Criteria

  • All 10 essential examples implemented
  • Each example builds and runs successfully
  • Top-level README with "Quick Start" section
  • Per-example READMEs with explanation and expected output
  • Build system integrated (zig build run-simple-call, etc.)
  • Code follows project style (snake_case, explicit errors, comments)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions