Skip to content

A toy version of the EVM interpreter from scratch with GETH being reference for Gas metering, Memory resizing and Bitmap construction for JUMP validation

Notifications You must be signed in to change notification settings

sunguru98/minimal-evm-interpreter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EIP-7702 EVM Interpreter

A custom Ethereum Virtual Machine (EVM) interpreter written in Rust, specifically designed to solve EIP-7702 CTF challenges. This implementation focuses on simulating EIP-7702 account delegation behavior for educational and CTF purposes.

Overview

This project implements a simplified EVM interpreter that can execute Ethereum bytecode and simulate the behavior of EIP-7702 account delegation. The interpreter was built to understand and solve CTF challenges involving the new account abstraction features introduced in EIP-7702.

Key Features

  • Complete EVM Instruction Set: Implements most EVM opcodes including arithmetic, memory, storage, control flow, and blockchain operations
  • EIP-7702 Simulation: Special handling for CALL opcodes to simulate account delegation behavior
  • Stack & Memory Management: Full implementation of EVM stack and memory with proper bounds checking
  • Gas Accounting: Comprehensive gas calculation system following Ethereum's gas model
  • Jump Analysis: Bytecode analysis for valid jump destinations using bitmap optimization
  • Debug Output: Detailed execution tracing for understanding contract behavior

Architecture

Core Components

Interpreter (interpreter.rs)

The main execution engine that runs EVM bytecode. It maintains:

  • Program counter and execution state
  • Stack, memory, and storage
  • Gas accounting
  • Call data and return data handling

OpCode (opcodes.rs)

Enum defining all supported EVM opcodes with bidirectional conversion between opcodes and their byte representations.

Data Structures

  • Stack (data/stack.rs): EVM stack with 1024 element limit
  • Memory (data/memory.rs): Expandable memory with gas-efficient operations
  • Storage: Persistent key-value storage simulation

Instruction Implementations (instructions/)

Modular implementation of EVM opcodes grouped by category:

  • Arithmetic: ADD, SUB, MUL, DIV, MOD, EXP, etc.
  • Bitwise: AND, OR, NOT, SHL, SHR, BYTE
  • Comparison: LT, GT, EQ, ISZERO
  • Memory: MLOAD, MSTORE, MSIZE, MCOPY
  • Storage: SLOAD, SSTORE
  • Control Flow: JUMP, JUMPI, JUMPDEST
  • Blockchain: ADDRESS, BALANCE, CALLER, TIMESTAMP, etc.
  • Call Data: CALLDATALOAD, CALLDATASIZE

EIP-7702 Implementation

The key insight for solving EIP-7702 CTF challenges is in the op_call function in instructions/blockchain.rs:

pub fn op_call(interpreter: &mut Interpreter) -> Result<()> {
    // ... pop call parameters from stack ...

    /*
       Here the Flag contract expects to call the EOA with calldata 0x0e8c21a3
       As per EIP-7702. A contract should be delegated to EOA with fallback function.
       This mimicks that. (Setting the stack as 1 meaning execution went through),
       and returning ABI bytes of boolean true, as that is what is being expected
       in the flag contract
    */
    let mut true_data = vec![0u8; 32];
    true_data[31] = 1;

    interpreter.return_data = Some(true_data);
    interpreter.stack.push(U256::one())?;

    Ok(())
}

This simulates the behavior where:

  1. A CTF contract calls an EOA with function selector 0x0e8c21a3
  2. The EOA has been delegated to a smart contract via EIP-7702
  3. The delegated contract returns true to satisfy the CTF condition

Usage

Prerequisites

  • Rust 1.70+ with Cargo
  • anyhow for error handling
  • primitive-types for U256 support
  • sha3 for Keccak256 hashing
  • hex for hexadecimal encoding/decoding
  • chrono for timestamp operations

Running the Interpreter

  1. Prepare bytecode: Create a bytecode.txt file containing the hex-encoded bytecode to execute (with or without 0x prefix)

  2. Execute:

cargo run

Execution Flow

The main execution follows this pattern:

  1. Deployment Simulation: Runs constructor bytecode and captures deployed runtime code
  2. Runtime Execution: Executes the runtime bytecode
  3. Flag Function Call: Calls the flag() function (selector: 0x178bdc5c) to verify the solution

EIP-7702 CTF Solution Strategy

When solving EIP-7702 CTF challenges:

  1. Analyze the Challenge Contract: Look for calls to specific function selectors (commonly 0x0e8c21a3)
  2. Deploy Delegation Contract: Create a contract with a fallback function that returns true
  3. Perform EIP-7702 Delegation: Use Foundry's cast commands:
# Sign authorization for delegation
cast wallet sign-auth <IMPLEMENTATION_CONTRACT_ADDRESS> \
  --private-key <DELEGATOR_PRIVATE_KEY> \
  --rpc-url <SEPOLIA_RPC_URL>

# Send delegation transaction
cast send $(cast az) \
  --private-key <SENDER_PRIVATE_KEY> \
  --auth $(cast wallet sign-auth <IMPLEMENTATION_CONTRACT_ADDRESS> --private-key <DELEGATOR_PRIVATE_KEY>) \
  --rpc-url <SEPOLIA_RPC_URL>
  1. Verify Delegation: Check that your EOA now has delegation code:
cast code <YOUR_EOA_ADDRESS> --rpc-url <SEPOLIA_RPC_URL>
# Should return: 0xef0100<implementation_address>
  1. Trigger Challenge: Call the challenge contract's solve function

Example Delegation Contract

For most EIP-7702 CTFs, this Solidity contract works:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CTFSolver {
    fallback(bytes calldata) external returns (bytes memory) {
        return abi.encode(true);
    }
}

Gas System

The interpreter implements Ethereum's gas model:

  • Constant Gas: Fixed costs for simple operations
  • Dynamic Gas: Variable costs based on data size (memory expansion, etc.)
  • Memory Expansion: Quadratic cost model for memory growth
  • Gas Limit: Configurable execution limit

Debugging Features

The interpreter provides extensive debugging output:

  • Opcode execution trace with hex values
  • Stack state after each operation
  • Memory contents (hex encoded)
  • Storage state
  • Return data tracking
  • Program counter progression

File Structure

src/
├── main.rs                    # Entry point and execution flow
├── interpreter.rs             # Main EVM execution engine
├── opcodes.rs                # Opcode definitions and conversions
├── data/
│   ├── memory.rs             # EVM memory implementation
│   ├── stack.rs              # EVM stack implementation
│   └── mod.rs                # Data module exports
├── instructions/
│   ├── arithmetic.rs         # Math operations (ADD, SUB, MUL, etc.)
│   ├── binary.rs             # Bitwise operations (AND, OR, SHL, etc.)
│   ├── blockchain.rs         # Blockchain context (ADDRESS, CALLER, etc.)
│   ├── calldata.rs           # Call data operations
│   ├── control.rs            # Control flow (JUMP, JUMPI)
│   ├── equality.rs           # Comparison and hashing
│   ├── memory.rs             # Memory operations (MLOAD, MSTORE, etc.)
│   ├── stack.rs              # Stack operations (PUSH, POP, DUP, SWAP)
│   ├── storage.rs            # Storage operations (SLOAD, SSTORE)
│   └── mod.rs                # Instructions module exports
├── table/
│   ├── gas.rs                # Gas calculation functions
│   ├── jump.rs               # Operation jump table and gas definitions
│   ├── memory.rs             # Memory size calculation utilities
│   ├── stack.rs              # Stack size validation utilities
│   └── mod.rs                # Table module exports
└── utils/
    ├── analysis.rs           # Bytecode analysis and bitmap generation
    ├── math.rs               # Mathematical utilities
    └── mod.rs                # Utils module exports

Contributing

This interpreter was built for educational purposes and CTF solving. Feel free to extend it with additional opcodes or features as needed for your specific use cases.

Educational Value

This implementation serves as an excellent learning resource for:

  • Understanding EVM internals and opcode execution
  • Learning about EIP-7702 account delegation mechanisms
  • Implementing virtual machine interpreters in Rust
  • Gas accounting and memory management in blockchain systems
  • Bytecode analysis and optimization techniques

The modular design makes it easy to understand each component independently while seeing how they work together to form a complete EVM implementation.

About

A toy version of the EVM interpreter from scratch with GETH being reference for Gas metering, Memory resizing and Bitmap construction for JUMP validation

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages