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.
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.
- Complete EVM Instruction Set: Implements most EVM opcodes including arithmetic, memory, storage, control flow, and blockchain operations
- EIP-7702 Simulation: Special handling for
CALLopcodes 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
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
Enum defining all supported EVM opcodes with bidirectional conversion between opcodes and their byte representations.
Stack(data/stack.rs): EVM stack with 1024 element limitMemory(data/memory.rs): Expandable memory with gas-efficient operationsStorage: Persistent key-value storage simulation
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
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:
- A CTF contract calls an EOA with function selector
0x0e8c21a3 - The EOA has been delegated to a smart contract via EIP-7702
- The delegated contract returns
trueto satisfy the CTF condition
- Rust 1.70+ with Cargo
anyhowfor error handlingprimitive-typesfor U256 supportsha3for Keccak256 hashinghexfor hexadecimal encoding/decodingchronofor timestamp operations
-
Prepare bytecode: Create a
bytecode.txtfile containing the hex-encoded bytecode to execute (with or without0xprefix) -
Execute:
cargo runThe main execution follows this pattern:
- Deployment Simulation: Runs constructor bytecode and captures deployed runtime code
- Runtime Execution: Executes the runtime bytecode
- Flag Function Call: Calls the
flag()function (selector:0x178bdc5c) to verify the solution
When solving EIP-7702 CTF challenges:
- Analyze the Challenge Contract: Look for calls to specific function selectors (commonly
0x0e8c21a3) - Deploy Delegation Contract: Create a contract with a fallback function that returns
true - 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>- 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>- Trigger Challenge: Call the challenge contract's solve function
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);
}
}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
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
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 exportsThis 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.
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.