From 92e71e5422583d9e049b199660cfe9861bd3daaa Mon Sep 17 00:00:00 2001 From: William Cory Date: Sun, 5 Oct 2025 00:14:55 -0700 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=94=A7=20chore:=20Remove=20unused=20f?= =?UTF-8?q?iles=20and=20documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed obsolete main.zig entry point and test utilities documentation as part of codebase cleanup. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/_test_utils/README.md | 87 --------------------------------------- src/main.zig | 40 ------------------ 2 files changed, 127 deletions(-) delete mode 100644 src/_test_utils/README.md delete mode 100644 src/main.zig diff --git a/src/_test_utils/README.md b/src/_test_utils/README.md deleted file mode 100644 index d30792397..000000000 --- a/src/_test_utils/README.md +++ /dev/null @@ -1,87 +0,0 @@ -# Test Utilities - -Testing infrastructure and fixtures for the Guillotine EVM. - -## Overview - -This module provides simple, self‑contained helpers and bytecode/calldata fixtures. Tests are driven via the Zig build system (zig build test). Avoid generic “test frameworks”; favor explicit, local setup as per project standards. - -## Components - -### Core Utilities -- `test_runner.zig` — Orchestrates focused test suites -- `trace_diff.zig` — Differential testing against REVM when enabled -- `test_fixtures.zig` — Minimal helpers to load embedded fixtures - -### Fixtures Directory (`fixtures/`) -Contains pre-generated test cases for various EVM operations: - -#### Opcode Testing -- **opcodes-arithmetic/** - Arithmetic operation test cases -- **opcodes-arithmetic-advanced/** - Complex arithmetic scenarios -- **opcodes-bitwise/** - Bitwise operation tests -- **opcodes-comparison/** - Comparison operator tests -- **opcodes-context/** - Context-related opcode tests -- **opcodes-control/** - Control flow operation tests -- **opcodes-crypto/** - Cryptographic operation tests -- **opcodes-data/** - Data manipulation tests -- **opcodes-dup/** - DUP opcode tests -- **opcodes-environmental-1/** - Environmental info tests (part 1) -- **opcodes-environmental-2/** - Environmental info tests (part 2) -- **opcodes-jump-basic/** - Basic jump operation tests -- **opcodes-log/** - LOG opcode tests -- **opcodes-memory/** - Memory operation tests -- **opcodes-push-pop/** - PUSH/POP operation tests -- **opcodes-storage-cold/** - Cold storage access tests -- **opcodes-storage-warm/** - Warm storage access tests -- **opcodes-swap/** - SWAP opcode tests -- **opcodes-system/** - System operation tests -- **opcodes-block-1/** - Block-related opcodes (part 1) -- **opcodes-block-2/** - Block-related opcodes (part 2) - -#### Precompile Testing -- **precompile-ecrecover/** - ECRECOVER precompile tests -- **precompile-sha256/** - SHA256 precompile tests -- **precompile-ripemd160/** - RIPEMD160 precompile tests -- **precompile-identity/** - Identity precompile tests -- **precompile-modexp/** - ModExp precompile tests -- **precompile-bn256add/** - BN256Add precompile tests -- **precompile-bn256mul/** - BN256Mul precompile tests -- **precompile-bn256pairing/** - BN256Pairing precompile tests -- **precompile-blake2f/** - Blake2F precompile tests -- **precompile-pointevaluation/** - Point evaluation precompile tests - -#### Contract Testing -- **erc20-transfer/** - ERC20 transfer tests -- **erc20-mint/** - ERC20 minting tests -- **erc20-approval-transfer/** - ERC20 approval and transfer tests -- **weth-mainnet/** - WETH contract tests -- **usdc-proxy/** - USDC proxy contract tests - -#### DeFi Protocol Testing -- **uniswap-v2-router/** - Uniswap V2 router tests -- **uniswap-v3-pool-eth-usdc/** - Uniswap V3 pool tests -- **aave-v3-pool/** - Aave V3 pool tests -- **compound-cusdc/** - Compound cUSDC tests - -#### Performance Testing -- **ten-thousand-hashes/** - Performance test with 10k hash operations -- **snailtracer/** - Complex execution tracing tests - -## Usage - -Import utilities and embed fixture files directly in tests: -```zig -const std = @import("std"); -// Embed hex-encoded test bytecode/calldata -const bytecode_hex = @embedFile("fixtures/opcodes-arithmetic/bytecode.txt"); -const calldata_hex = @embedFile("fixtures/opcodes-arithmetic/calldata.txt"); -``` - -## Testing Philosophy - -- Differential testing where it adds value (REVM harness) -- Fixture‑based coverage for opcodes and system paths -- Self‑contained tests (no shared helpers unless required) -- Evidence‑based debugging; increase visibility before fixes -- By default, Zig tests emit nothing on success diff --git a/src/main.zig b/src/main.zig deleted file mode 100644 index 668b49266..000000000 --- a/src/main.zig +++ /dev/null @@ -1,40 +0,0 @@ -//! By convention, main.zig is where your main function lives in the case that -//! you are building an executable. If you are making a library, the convention -//! is to delete this file and start with root.zig instead. -const std = @import("std"); - -pub fn main() !void { - // Prints to log - const log = @import("Guillotine_lib").log; - log.info("All your {s} are belong to us.", .{"codebase"}); - - // stdout is for the actual output of your application, for example if you - // are implementing gzip, then only the compressed bytes should be sent to - // stdout, not any debugging messages. - var stdout_buffer: [4096]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); - const stdout = &stdout_writer.interface; - - try stdout.print("Run `zig build test` to run the tests.\n", .{}); -} - -test "simple test" { - var list = std.ArrayList(i32){}; - defer list.deinit(std.testing.allocator); // Try commenting this out and see if zig detects the memory leak! - try list.append(std.testing.allocator, 42); - try std.testing.expectEqual(@as(i32, 42), list.pop()); -} - -// test "fuzz example" { -// const Context = struct { -// fn test_one(context: @This(), input: []const u8) anyerror!void { -// _ = context; -// // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case! -// try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input)); -// } -// }; -// try std.testing.fuzz(Context{}, Context.test_one, .{}); -// } - -/// This imports the separate module containing `root.zig`. Take a look in `build.zig` for details. -const lib = @import("Guillotine_lib"); From f58cb8dd7ed9cd7d096a4fd35e3f1d516c10166f Mon Sep 17 00:00:00 2001 From: William Cory Date: Sun, 5 Oct 2025 00:15:04 -0700 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=94=A8=20build:=20Expand=20build=20co?= =?UTF-8?q?nfiguration=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive build-time configuration options including: - EIP overrides for fine-grained hardfork control - Call/stack limits (max_input_size, etc.) - Memory and arena allocator tuning - Tracer configuration with presets - System contract toggles (beacon roots, historical hashes, etc.) - SIMD vector length configuration - Loop safety quota settings Organizes options into logical groups with detailed comments for better discoverability. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- build.zig | 50 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/build.zig b/build.zig index 037ed0adf..e795d54ec 100644 --- a/build.zig +++ b/build.zig @@ -64,19 +64,61 @@ pub fn build(b: *std.Build) void { const build_options = b.addOptions(); build_options.addOption(bool, "enable_tracing", b.option(bool, "enable-tracing", "Enable EVM instruction tracing") orelse false); build_options.addOption(bool, "disable_tailcall_dispatch", b.option(bool, "disable-tailcall-dispatch", "Disable tailcall-based dispatch") orelse true); + + // EIPs & Hardforks build_options.addOption([]const u8, "hardfork", b.option([]const u8, "evm-hardfork", "EVM hardfork (default: CANCUN)") orelse "CANCUN"); - build_options.addOption(bool, "disable_gas_checks", b.option(bool, "evm-disable-gas", "Disable gas checks for testing") orelse false); - build_options.addOption(bool, "enable_fusion", b.option(bool, "evm-enable-fusion", "Enable bytecode fusion") orelse true); - build_options.addOption([]const u8, "optimize_strategy", b.option([]const u8, "evm-optimize", "EVM optimization strategy") orelse "safe"); + build_options.addOption([]const u8, "eip_overrides", b.option([]const u8, "evm-eip-overrides", "EIP overrides (format: '3855:true,1153:false')") orelse ""); + + // Call & Stack Limits build_options.addOption(u11, "max_call_depth", b.option(u11, "max-call-depth", "Maximum call depth (default: 1024)") orelse 1024); + build_options.addOption(u18, "max_input_size", b.option(u18, "max-input-size", "Maximum input size (default: 131072)") orelse 131072); build_options.addOption(u12, "stack_size", b.option(u12, "stack-size", "Maximum stack size (default: 1024)") orelse 1024); + + // Code Size Limits build_options.addOption(u32, "max_bytecode_size", b.option(u32, "max-bytecode-size", "Maximum bytecode size (default: 24576)") orelse 24576); build_options.addOption(u32, "max_initcode_size", b.option(u32, "max-initcode-size", "Maximum initcode size (default: 49152)") orelse 49152); + + // Gas & Balance build_options.addOption(u64, "block_gas_limit", b.option(u64, "block-gas-limit", "Block gas limit (default: 30000000)") orelse 30_000_000); + build_options.addOption(bool, "disable_gas_checks", b.option(bool, "evm-disable-gas", "Disable gas checks for testing") orelse false); + build_options.addOption(bool, "disable_balance_checks", b.option(bool, "disable-balance-checks", "Disable balance checks") orelse false); + + // Memory Configuration build_options.addOption(usize, "memory_initial_capacity", b.option(usize, "memory-initial-capacity", "Memory initial capacity (default: 4096)") orelse 4096); build_options.addOption(u64, "memory_limit", b.option(u64, "memory-limit", "Memory limit (default: 0xFFFFFF)") orelse 0xFFFFFF); + + // Arena Allocator build_options.addOption(usize, "arena_capacity_limit", b.option(usize, "arena-capacity-limit", "Arena capacity limit (default: 64MB)") orelse (64 * 1024 * 1024)); - build_options.addOption(bool, "disable_balance_checks", b.option(bool, "disable-balance-checks", "Disable balance checks") orelse false); + build_options.addOption(u32, "arena_growth_factor", b.option(u32, "arena-growth-factor", "Arena growth factor percentage (default: 150)") orelse 150); + + // Optimization & Features + build_options.addOption([]const u8, "optimize_strategy", b.option([]const u8, "evm-optimize", "EVM optimization strategy") orelse "safe"); + build_options.addOption(bool, "enable_fusion", b.option(bool, "evm-enable-fusion", "Enable bytecode fusion") orelse true); + build_options.addOption(bool, "enable_precompiles", b.option(bool, "evm-enable-precompiles", "Enable precompiles (default: true)") orelse true); + build_options.addOption(u32, "vector_length", b.option(u32, "vector-length", "SIMD vector length (default: 0 = auto)") orelse 0); + + // Loop Safety + build_options.addOption(?u32, "loop_quota", b.option(u32, "loop-quota", "Loop safety quota (default: null for release, 1000000 for debug)")); + + // Block Info Configuration + build_options.addOption(bool, "block_info_use_compact_types", b.option(bool, "block-info-use-compact-types", "Use u64 for difficulty/base_fee (default: false)") orelse false); + + // System Contracts + build_options.addOption(bool, "enable_beacon_roots", b.option(bool, "enable-beacon-roots", "Enable EIP-4788 beacon roots (default: true)") orelse true); + build_options.addOption(bool, "enable_historical_block_hashes", b.option(bool, "enable-historical-block-hashes", "Enable EIP-2935 historical hashes (default: true)") orelse true); + build_options.addOption(bool, "enable_validator_deposits", b.option(bool, "enable-validator-deposits", "Enable validator deposits (default: true)") orelse true); + build_options.addOption(bool, "enable_validator_withdrawals", b.option(bool, "enable-validator-withdrawals", "Enable validator withdrawals (default: true)") orelse true); + + // Tracer Configuration + build_options.addOption(bool, "tracer_enabled", b.option(bool, "enable-tracer", "Enable tracer system (default: false)") orelse false); + build_options.addOption(bool, "tracer_validation", b.option(bool, "tracer-validation", "Enable tracer validation (default: false)") orelse false); + build_options.addOption(bool, "tracer_step_capture", b.option(bool, "tracer-step-capture", "Enable tracer step capture (default: false)") orelse false); + build_options.addOption(bool, "tracer_pc_tracking", b.option(bool, "tracer-pc-tracking", "Enable tracer PC tracking (default: false)") orelse false); + build_options.addOption(bool, "tracer_gas_tracking", b.option(bool, "tracer-gas-tracking", "Enable tracer gas tracking (default: false)") orelse false); + build_options.addOption(bool, "tracer_debug_logging", b.option(bool, "tracer-debug-logging", "Enable tracer debug logging (default: false)") orelse false); + build_options.addOption(bool, "tracer_advanced_trace", b.option(bool, "tracer-advanced-trace", "Enable tracer advanced trace (default: false)") orelse false); + build_options.addOption([]const u8, "tracer_preset", b.option([]const u8, "tracer-preset", "Tracer preset: disabled, debug, full") orelse ""); + const options_mod = build_options.createModule(); const rust_target = b: { From 90af56d744ce9e9b7bef685414651dfb0ecfd507 Mon Sep 17 00:00:00 2001 From: William Cory Date: Sun, 5 Oct 2025 00:15:13 -0700 Subject: [PATCH 3/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20Enhance=20?= =?UTF-8?q?EVM=20configuration=20system?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement comprehensive configuration parsing from build options: - Add parseEipOverrides() for runtime EIP override parsing - Add parseTracerConfig() for tracer preset handling - Wire up all new build options in fromBuildOptions() - Support block info compact types configuration - Add system contract feature toggles - Remove obsolete TODO comments Configuration now supports all build-time flags with proper type safety and validation. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/evm_config.zig | 134 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 121 insertions(+), 13 deletions(-) diff --git a/src/evm_config.zig b/src/evm_config.zig index 549beb878..a7ef08dda 100644 --- a/src/evm_config.zig +++ b/src/evm_config.zig @@ -11,7 +11,6 @@ const Address = primitives.Address; const SafetyCounter = @import("internal/safety_counter.zig").SafetyCounter; const Mode = @import("internal/safety_counter.zig").Mode; - /// Custom precompile implementation pub const PrecompileOverride = struct { address: Address, @@ -217,8 +216,86 @@ pub const EvmConfig = struct { }; } - // TODO: This is either dead code or code that should be dead - // Remove it + /// Parse EIP overrides from build options string format + /// Format: "3855:true,1153:false,6780:false" + fn parseEipOverrides(comptime override_str: []const u8) []const EipOverride { + if (override_str.len == 0) return &.{}; + + // Count commas to determine array size + comptime var count: usize = 1; + comptime for (override_str) |c| { + if (c == ',') count += 1; + }; + + comptime var overrides: [count]EipOverride = undefined; + comptime var idx: usize = 0; + comptime var start: usize = 0; + + comptime for (override_str, 0..) |c, i| { + if (c == ',' or i == override_str.len - 1) { + const end = if (c == ',') i else i + 1; + const segment = override_str[start..end]; + + // Find colon separator + var colon_pos: usize = 0; + for (segment, 0..) |sc, si| { + if (sc == ':') { + colon_pos = si; + break; + } + } + + const eip_str = segment[0..colon_pos]; + const enabled_str = segment[colon_pos + 1 ..]; + + // Parse EIP number + var eip_num: u16 = 0; + for (eip_str) |digit| { + if (digit >= '0' and digit <= '9') { + eip_num = eip_num * 10 + (digit - '0'); + } + } + + // Parse boolean + const enabled = std.mem.eql(u8, enabled_str, "true"); + + overrides[idx] = EipOverride{ .eip = eip_num, .enabled = enabled }; + idx += 1; + start = i + 1; + } + }; + + const result = overrides; + return &result; + } + + /// Parse tracer configuration from preset string + fn parseTracerConfig(comptime preset: []const u8, comptime build_opts: anytype) @import("tracer/tracer.zig").TracerConfig { + const TracerConfig = @import("tracer/tracer.zig").TracerConfig; + + // If preset is specified, use it + if (preset.len > 0) { + if (std.mem.eql(u8, preset, "full")) { + return TracerConfig.full; + } else if (std.mem.eql(u8, preset, "debug")) { + return TracerConfig.debug; + } else if (std.mem.eql(u8, preset, "disabled")) { + return TracerConfig.disabled; + } + } + + // Otherwise, build from individual flags + return TracerConfig{ + .enabled = build_opts.tracer_enabled, + .enable_validation = build_opts.tracer_validation, + .enable_step_capture = build_opts.tracer_step_capture, + .enable_pc_tracking = build_opts.tracer_pc_tracking, + .enable_gas_tracking = build_opts.tracer_gas_tracking, + .enable_debug_logging = build_opts.tracer_debug_logging, + .enable_advanced_trace = build_opts.tracer_advanced_trace, + }; + } + /// Generate configuration from build options pub fn fromBuildOptions() EvmConfig { const build_options = @import("build_options"); @@ -232,30 +309,61 @@ pub const EvmConfig = struct { else EvmConfig{}; // safe/default + // Parse EIP overrides from build options + const eip_overrides = parseEipOverrides(build_options.eip_overrides); + // Apply build options config.eips = Eips{ .hardfork = getHardforkFromString(build_options.hardfork), - .overrides = config.eip_overrides, + .overrides = eip_overrides, }; + config.eip_overrides = eip_overrides; + + // Call & Stack Limits config.max_call_depth = build_options.max_call_depth; + config.max_input_size = build_options.max_input_size; config.stack_size = build_options.stack_size; + + // Code Size Limits config.max_bytecode_size = build_options.max_bytecode_size; config.max_initcode_size = build_options.max_initcode_size; + + // Gas & Balance config.block_gas_limit = build_options.block_gas_limit; + config.disable_gas_checks = build_options.disable_gas_checks; + config.disable_balance_checks = build_options.disable_balance_checks; + + // Memory Configuration config.memory_initial_capacity = build_options.memory_initial_capacity; config.memory_limit = build_options.memory_limit; + + // Arena Allocator config.arena_capacity_limit = build_options.arena_capacity_limit; + config.arena_growth_factor = build_options.arena_growth_factor; + + // Optimization & Features config.enable_fusion = build_options.enable_fusion; - config.enable_precompiles = true; // Always enable precompiles - config.disable_gas_checks = build_options.disable_gas_checks; - config.disable_balance_checks = build_options.disable_balance_checks; + config.enable_precompiles = build_options.enable_precompiles; + config.vector_length = build_options.vector_length; - // Set tracer if enabled - if (build_options.enable_tracing) { - // For now, we'll leave TracerType as null since it requires more complex setup - // Users can still set up their own tracer through the configuration - // Tracer is now part of EVM struct, not config - } + // Loop Safety + config.loop_quota = build_options.loop_quota; + + // Block Info Configuration + config.block_info_config = .{ + .DifficultyType = if (build_options.block_info_use_compact_types) u64 else u256, + .BaseFeeType = if (build_options.block_info_use_compact_types) u64 else u256, + .use_compact_types = build_options.block_info_use_compact_types, + }; + + // System Contracts + config.enable_beacon_roots = build_options.enable_beacon_roots; + config.enable_historical_block_hashes = build_options.enable_historical_block_hashes; + config.enable_validator_deposits = build_options.enable_validator_deposits; + config.enable_validator_withdrawals = build_options.enable_validator_withdrawals; + + // Tracer Configuration + config.tracer_config = parseTracerConfig(build_options.tracer_preset, build_options); return config; } From 888d01e5683a954df299f109377cc94688dbb343 Mon Sep 17 00:00:00 2001 From: William Cory Date: Sun, 5 Oct 2025 00:15:20 -0700 Subject: [PATCH 4/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20Simplify?= =?UTF-8?q?=20root=20module=20structure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Streamline root.zig to only expose essential EVM types and test aggregation, removing redundant re-exports and documentation. Update root_c.zig C API implementation to align with simplified module structure. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/root.zig | 120 --------- src/root_c.zig | 676 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 510 insertions(+), 286 deletions(-) diff --git a/src/root.zig b/src/root.zig index 8ff68f870..d1aedd700 100644 --- a/src/root.zig +++ b/src/root.zig @@ -1,128 +1,8 @@ -//! Guillotine - High-Performance Ethereum Virtual Machine in Zig -//! -//! This is the main entry point for the Guillotine implementation. -//! It re-exports all the modules from their new organized structure. - const std = @import("std"); -// Re-export everything from evm/root.zig -pub const Frame = @import("frame/frame.zig").Frame; -pub const FrameConfig = @import("frame/frame_config.zig").FrameConfig; -pub const FrameDispatch = @import("preprocessor/dispatch.zig").Dispatch; -pub const frame_mod = @import("frame/frame.zig"); -pub const frame = @import("frame/frame.zig"); -pub const frame_handlers = @import("frame/frame_handlers.zig"); - -// Stack and memory modules -pub const StackConfig = @import("stack/stack_config.zig").StackConfig; -pub const Stack = @import("stack/stack.zig").Stack; -pub const MemoryConfig = @import("memory/memory_config.zig").MemoryConfig; -pub const Memory = @import("memory/memory.zig").Memory; -pub const MemoryError = @import("memory/memory.zig").MemoryError; - -// EVM main module and configuration pub const Evm = @import("evm.zig").Evm; pub const EvmConfig = @import("evm_config.zig").EvmConfig; -// Build-configured EVM -pub const getBuildConfig = EvmConfig.fromBuildOptions; -pub const BuildConfiguredEvm = Evm(EvmConfig.fromBuildOptions()); - -// Pre-configured EVM types for FFI -// These are concrete types that can be stored at runtime -pub const MainnetEvm = Evm(.{}); - -pub const MainnetEvmWithTracer = Evm(EvmConfig{ - .eips = .{ .hardfork = @import("eips_and_hardforks/eips.zig").Hardfork.CANCUN }, - .tracer_config = @import("tracer/tracer.zig").TracerConfig{ - .enabled = true, - .enable_validation = true, // Enable validation for step capture - .enable_step_capture = true, // Enable step capture for JSON-RPC trace - .enable_pc_tracking = true, - .enable_gas_tracking = true, - .enable_debug_logging = false, // Disable debug logging to reduce noise - .enable_advanced_trace = false, // Disable advanced trace to simplify - }, -}); - -pub const TestEvm = Evm(EvmConfig{ - .eips = .{ .hardfork = @import("eips_and_hardforks/eips.zig").Hardfork.CANCUN }, - .tracer_config = @import("tracer/tracer.zig").TracerConfig{ - .enabled = true, - .enable_validation = true, - .enable_pc_tracking = true, - .enable_gas_tracking = true, - .enable_debug_logging = false, // Disable debug logging to reduce noise - }, - .disable_gas_checks = true, -}); - -// Fixtures for testing -pub const FixtureContract = @import("fixtures").FixtureContract; -pub const ContractName = @import("fixtures").ContractName; - -// Default EVM types - now uses build configuration -pub const DefaultEvm = BuildConfiguredEvm; - -// Tracer modules -pub const tracer = @import("tracer/tracer.zig"); -pub const Tracer = @import("tracer/tracer.zig").Tracer; -pub const TracerConfig = @import("tracer/tracer.zig").TracerConfig; -pub const MinimalEvm = @import("tracer/minimal_evm.zig").MinimalEvm; -// Compatibility alias - to be removed later -pub const DefaultTracer = Tracer; -// differential_tracer removed - using MinimalEvm for differential testing -pub const JSONRPCTracer = @import("tracer/tracer.zig").JSONRPCTracer; - -// Logging -pub const log = @import("log.zig"); - -// Bytecode modules -pub const BytecodeConfig = @import("bytecode/bytecode_config.zig").BytecodeConfig; -pub const Bytecode = @import("bytecode/bytecode.zig").Bytecode; -pub const BytecodeStats = @import("bytecode/bytecode_stats.zig").BytecodeStats; -pub const bytecode = @import("bytecode/bytecode.zig"); - -// Dispatch module -pub const dispatch = @import("preprocessor/dispatch.zig"); - -// Opcode and instruction data -pub const Opcode = @import("opcodes/opcode.zig").Opcode; -pub const OpcodeData = @import("opcodes/opcode_data.zig"); -pub const OpcodeSynthetic = @import("opcodes/opcode_synthetic.zig"); -pub const opcode_synthetic = @import("opcodes/opcode_synthetic.zig"); -pub const opcode_data = @import("opcodes/opcode_data.zig"); - -// Precompiles module -pub const precompiles = @import("precompiles/precompiles.zig"); - -// Database and state modules -pub const BlockInfo = @import("block/block_info.zig").BlockInfo(.{}); -pub const CompactBlockInfo = @import("block/block_info.zig").BlockInfo(.{ .use_compact_types = true }); -pub const DefaultBlockInfo = BlockInfo; // Alias for compatibility -pub const BlockInfoConfig = @import("frame/block_info_config.zig").BlockInfoConfig; -pub const CallParams = @import("frame/call_params.zig").CallParams({}); -pub const CallResult = @import("frame/call_result.zig").CallResult({}); -pub const CreatedContracts = @import("storage/created_contracts.zig").CreatedContracts; -pub const Database = @import("storage/database.zig").Database; -pub const Account = @import("storage/database_interface_account.zig").Account; -pub const AccessList = @import("storage/access_list.zig").AccessList; -pub const Hardfork = @import("eips_and_hardforks/eips.zig").Hardfork; -pub const Eips = @import("eips_and_hardforks/eips.zig").Eips; -pub const MemoryDatabase = @import("storage/memory_database.zig").MemoryDatabase; -pub const SelfDestruct = @import("storage/self_destruct.zig").SelfDestruct; -pub const Log = @import("primitives").logs.Log; -pub const TransactionContext = @import("block/transaction_context.zig").TransactionContext; -pub const AuthorizationProcessor = @import("eips_and_hardforks/authorization_processor.zig").AuthorizationProcessor; -pub const AuthorizationError = @import("eips_and_hardforks/authorization_processor.zig").AuthorizationError; -pub const kzg_setup = @import("precompiles/kzg_setup.zig"); - -// Re-export from evm module for compatibility -pub const Primitives = @import("primitives"); -pub const Provider = @import("provider"); -pub const dispatch_pretty_print = @import("preprocessor/dispatch_pretty_print.zig"); - -// Run tests test { // Test EVM modules _ = @import("evm_tests.zig"); diff --git a/src/root_c.zig b/src/root_c.zig index dd7924b1b..6b73e9eba 100644 --- a/src/root_c.zig +++ b/src/root_c.zig @@ -1,221 +1,565 @@ +//! C API for Guillotine EVM +//! Provides a simple, ergonomic C-compatible interface + +const std = @import("std"); +const root = @import("root.zig"); +const primitives = @import("primitives"); + +// The C API expects the user to pass in build time flags +const config = root.EvmConfig.fromBuildOptions(); +const Evm = root.Evm(config); +const Database = @import("storage/database.zig").Database; +const BlockInfoNative = @import("block/block_info.zig").BlockInfo(.{}); +const TransactionContextNative = @import("block/transaction_context.zig").TransactionContext; + // ============================================================================ -// EVM C API ROOT - Main entry point for C API exports +// C-Compatible Types // ============================================================================ -// -// This file serves as the main entry point for the EVM C API. It re-exports -// all C API modules to create a unified interface for C/FFI consumers. -// -// Usage: -// zig build-lib -dynamic root_c.zig # Creates shared library -// zig build-lib root_c.zig # Creates static library -// -// The generated library can be used from C, Python, JavaScript, Go, Rust, etc. -const std = @import("std"); -const frame_c = @import("frame/frame_c.zig"); -const bytecode_c = @import("bytecode/bytecode_c.zig"); -const memory_c = @import("memory/memory_c.zig"); -const stack_c = @import("stack/stack_c.zig"); -const precompiles_c = @import("precompiles/precompiles_c.zig"); -const hardfork_c = @import("eips_and_hardforks/hardfork_c.zig"); - -// Export all C API modules -// Note: In Zig 0.15.1, usingnamespace is removed. We need to explicitly re-export. -// For C API compatibility, we re-export all public exports from each module. -comptime { - @export(frame_c.evm_frame_create, .{ .name = "evm_frame_create" }); - @export(frame_c.evm_frame_destroy, .{ .name = "evm_frame_destroy" }); - @export(frame_c.evm_frame_reset, .{ .name = "evm_frame_reset" }); - @export(frame_c.evm_frame_execute, .{ .name = "evm_frame_execute" }); - // Additional exports should be added as needed for each module -} - -const allocator = std.heap.c_allocator; +/// C-compatible address (20 bytes) +pub const CAddress = extern struct { + bytes: [20]u8, +}; + +/// C-compatible u256 (32 bytes, little-endian) +pub const CU256 = extern struct { + bytes: [32]u8, +}; + +/// C-compatible block info structure +pub const CBlockInfo = extern struct { + chain_id: u64, + number: u64, + parent_hash: [32]u8, + timestamp: u64, + difficulty: u64, + gas_limit: u64, + coinbase: CAddress, + base_fee: u64, + prev_randao: [32]u8, + blob_base_fee: u64, + // Note: beacon_root is optional, handle separately if needed +}; + +/// C-compatible transaction context +pub const CTransactionContext = extern struct { + gas_limit: u64, + coinbase: CAddress, + chain_id: u16, + blob_base_fee: CU256, +}; + +/// C-compatible execution result +pub const CExecutionResult = extern struct { + success: bool, + gas_used: u64, + gas_refunded: u64, + output_ptr: [*]const u8, + output_len: usize, + error_message: [*:0]const u8, +}; // ============================================================================ -// LIBRARY METADATA AND VERSION INFO +// Global State Management // ============================================================================ -/// Get library version string -pub export fn evm_version() [*:0]const u8 { - return "0.1.0"; +const CApiState = struct { + const Self = @This(); + + var singleton: ?*Self = null; + var init_mutex: std.Thread.Mutex = .{}; + + allocator: std.mem.Allocator, + gpa: std.heap.GeneralPurposeAllocator(.{}), + last_error: [512]u8 = undefined, + last_error_len: usize = 0, + // Keep track of allocated results for cleanup + last_result_output: ?[]const u8 = null, + + fn get_or_init() !*Self { + init_mutex.lock(); + defer init_mutex.unlock(); + + if (singleton) |state| { + return state; + } + + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + const state = try allocator.create(Self); + state.* = Self{ + .allocator = allocator, + .gpa = gpa, + .last_error = undefined, + .last_error_len = 0, + .last_result_output = null, + }; + + singleton = state; + return state; + } + + fn set_error(self: *Self, comptime fmt: []const u8, args: anytype) void { + const msg = std.fmt.bufPrint(&self.last_error, fmt, args) catch { + @memcpy(self.last_error[0.."Error formatting error message".len], "Error formatting error message"); + self.last_error_len = "Error formatting error message".len; + return; + }; + self.last_error_len = msg.len; + } +}; + +// ============================================================================ +// Initialization & Cleanup +// ============================================================================ + +/// Initialize the C API (must be called before any other functions) +/// Returns 0 on success, -1 on failure +export fn guillotine_init() c_int { + _ = CApiState.get_or_init() catch return -1; + return 0; } -/// Get library build info -pub export fn evm_build_info() [*:0]const u8 { - return "EVM C API - Built with Zig " ++ @import("builtin").zig_version_string; +/// Get the last error message +/// Returns a null-terminated string, valid until next API call +export fn guillotine_get_last_error() [*:0]const u8 { + const state = CApiState.get_or_init() catch return ""; + if (state.last_error_len == 0) return ""; + state.last_error[state.last_error_len] = 0; + return @ptrCast(&state.last_error); } -/// Initialize library (currently a no-op, but reserves API for future use) -pub export fn evm_init() c_int { - return 0; // Success +// ============================================================================ +// Database Management +// ============================================================================ + +/// Opaque database handle +pub const DatabaseHandle = opaque {}; + +/// Create a new in-memory database +/// Returns null on failure +export fn database_create() ?*DatabaseHandle { + const state = CApiState.get_or_init() catch return null; + + const db = state.allocator.create(Database) catch { + state.set_error("Failed to allocate database", .{}); + return null; + }; + + db.* = Database.init(state.allocator) catch { + state.allocator.destroy(db); + state.set_error("Failed to initialize database", .{}); + return null; + }; + + return @ptrCast(db); } -/// Cleanup library resources (currently a no-op, but reserves API for future use) -pub export fn evm_cleanup() void { - // Future: global cleanup if needed +/// Destroy a database and free its resources +export fn database_destroy(handle: ?*DatabaseHandle) void { + if (handle == null) return; + const state = CApiState.get_or_init() catch return; + + const db: *Database = @ptrCast(@alignCast(handle)); + db.deinit(); + state.allocator.destroy(db); } -// Ensure all C API modules are compiled and their tests are included when -// root_c.zig is the test root, mirroring root.zig style. -test "C API modules compile" { - std.testing.refAllDecls(frame_c); - std.testing.refAllDecls(bytecode_c); - std.testing.refAllDecls(memory_c); - std.testing.refAllDecls(stack_c); - std.testing.refAllDecls(precompiles_c); - std.testing.refAllDecls(hardfork_c); +/// Set account balance in the database +/// Returns 0 on success, -1 on failure +export fn database_set_balance(handle: ?*DatabaseHandle, address: *const CAddress, balance: *const CU256) c_int { + if (handle == null) return -1; + const state = CApiState.get_or_init() catch return -1; + + const db: *Database = @ptrCast(@alignCast(handle)); + const addr = primitives.Address{ .bytes = address.bytes }; + const bal = std.mem.readInt(u256, &balance.bytes, .little); + + db.setBalance(addr, bal) catch { + state.set_error("Failed to set balance", .{}); + return -1; + }; + + return 0; } -// ============================================================================ -// TESTING FUNCTIONS (DEBUG BUILDS ONLY) -// ============================================================================ +/// Set account code in the database +/// Returns 0 on success, -1 on failure +export fn database_set_code(handle: ?*DatabaseHandle, address: *const CAddress, code: [*]const u8, code_len: usize) c_int { + if (handle == null) return -1; + const state = CApiState.get_or_init() catch return -1; + + const db: *Database = @ptrCast(@alignCast(handle)); + const addr = primitives.Address{ .bytes = address.bytes }; + const code_slice = code[0..code_len]; + + db.setCode(addr, code_slice) catch { + state.set_error("Failed to set code", .{}); + return -1; + }; + + return 0; +} -/// Simple test function - executes PUSH1 5, PUSH1 10, ADD, STOP -pub export fn evm_test_simple_execution() c_int { - // Bytecode: PUSH1 5, PUSH1 10, ADD, STOP - const bytecode = [_]u8{ 0x60, 0x05, 0x60, 0x0A, 0x01, 0x00 }; +/// Set account nonce in the database +/// Returns 0 on success, -1 on failure +export fn database_set_nonce(handle: ?*DatabaseHandle, address: *const CAddress, nonce: u64) c_int { + if (handle == null) return -1; + const state = CApiState.get_or_init() catch return -1; - // Create frame directly (since export functions can't be called from Zig) - const handle = allocator.create(frame_c.FrameHandle) catch return -1; - errdefer allocator.destroy(handle); + const db: *Database = @ptrCast(@alignCast(handle)); + const addr = primitives.Address{ .bytes = address.bytes }; - // Copy bytecode - handle.bytecode_owned = allocator.dupe(u8, &bytecode) catch { - allocator.destroy(handle); + db.setNonce(addr, nonce) catch { + state.set_error("Failed to set nonce", .{}); return -1; }; - errdefer allocator.free(handle.bytecode_owned); - // Initialize frame interpreter - handle.interpreter = frame_c.FrameInterpreter.init(allocator, handle.bytecode_owned, 1000000, {}, frame_c.createCApiHost()) catch { - allocator.free(handle.bytecode_owned); - allocator.destroy(handle); + return 0; +} + +/// Set storage slot in the database +/// Returns 0 on success, -1 on failure +export fn database_set_storage(handle: ?*DatabaseHandle, address: *const CAddress, slot: *const CU256, value: *const CU256) c_int { + if (handle == null) return -1; + const state = CApiState.get_or_init() catch return -1; + + const db: *Database = @ptrCast(@alignCast(handle)); + const addr = primitives.Address{ .bytes = address.bytes }; + const slot_val = std.mem.readInt(u256, &slot.bytes, .little); + const storage_val = std.mem.readInt(u256, &value.bytes, .little); + + db.setStorage(addr, slot_val, storage_val) catch { + state.set_error("Failed to set storage", .{}); return -1; }; - handle.initial_gas = 1000000; - handle.is_stopped = false; + return 0; +} - defer { - handle.interpreter.deinit(allocator); - allocator.free(handle.bytecode_owned); - allocator.destroy(handle); - } +// ============================================================================ +// EVM Management +// ============================================================================ + +/// Opaque EVM handle +pub const EvmHandle = opaque {}; + +/// Create a new EVM instance +/// All parameters are required except database (if null, an internal one is created) +/// Returns null on failure +export fn evm_create( + database: ?*DatabaseHandle, + block_info: *const CBlockInfo, + transaction_context: *const CTransactionContext, + gas_price: *const CU256, + origin: *const CAddress, +) ?*EvmHandle { + const state = CApiState.get_or_init() catch return null; + + // Convert C types to Zig types + const native_block_info = BlockInfoNative{ + .chain_id = block_info.chain_id, + .number = block_info.number, + .parent_hash = block_info.parent_hash, + .timestamp = block_info.timestamp, + .difficulty = block_info.difficulty, + .gas_limit = block_info.gas_limit, + .coinbase = primitives.Address{ .bytes = block_info.coinbase.bytes }, + .base_fee = block_info.base_fee, + .prev_randao = block_info.prev_randao, + .blob_base_fee = block_info.blob_base_fee, + }; - // Execute - handle.interpreter.interpret() catch |err| { - handle.is_stopped = true; - return frame_c.zigErrorToCError(err); + const native_tx_context = TransactionContextNative{ + .gas_limit = transaction_context.gas_limit, + .coinbase = primitives.Address{ .bytes = transaction_context.coinbase.bytes }, + .chain_id = transaction_context.chain_id, + .blob_base_fee = std.mem.readInt(u256, &transaction_context.blob_base_fee.bytes, .little), }; - // Check that we have one value on stack (15) - if (handle.interpreter.frame.stack.size() != 1) return -100; + const native_gas_price = std.mem.readInt(u256, &gas_price.bytes, .little); + const native_origin = primitives.Address{ .bytes = origin.bytes }; + + // Get or create database + const db_ptr: *Database = if (database) |db_handle| + @ptrCast(@alignCast(db_handle)) + else blk: { + const new_db = state.allocator.create(Database) catch { + state.set_error("Failed to allocate database", .{}); + return null; + }; + new_db.* = Database.init(state.allocator) catch { + state.allocator.destroy(new_db); + state.set_error("Failed to initialize database", .{}); + return null; + }; + break :blk new_db; + }; - const value = handle.interpreter.frame.stack.pop() catch return -101; - if (value != 15) return -102; + // Create EVM + const evm_ptr = state.allocator.create(Evm) catch { + state.set_error("Failed to allocate EVM", .{}); + return null; + }; - return frame_c.EVM_SUCCESS; + evm_ptr.* = Evm.init( + state.allocator, + db_ptr, + native_block_info, + native_tx_context, + native_gas_price, + native_origin, + ) catch |err| { + state.allocator.destroy(evm_ptr); + state.set_error("Failed to initialize EVM: {any}", .{err}); + return null; + }; + + return @ptrCast(evm_ptr); } -/// Test stack operations -pub export fn evm_test_stack_operations() c_int { - // Empty bytecode (just for frame creation) - const bytecode = [_]u8{0x00}; // STOP +/// Execute a transaction (CALL) +/// Returns execution result (valid until next evm_transact call or evm_destroy) +export fn evm_transact( + handle: ?*EvmHandle, + caller: *const CAddress, + to: *const CAddress, + value: *const CU256, + data: [*]const u8, + data_len: usize, + gas_limit: u64, +) ?*const CExecutionResult { + if (handle == null) return null; + const state = CApiState.get_or_init() catch return null; + + const evm: *Evm = @ptrCast(@alignCast(handle)); + + const native_caller = primitives.Address{ .bytes = caller.bytes }; + const native_to = primitives.Address{ .bytes = to.bytes }; + const native_value = std.mem.readInt(u256, &value.bytes, .little); + const input_data = data[0..data_len]; + + // Clean up previous result + if (state.last_result_output) |old_output| { + state.allocator.free(old_output); + state.last_result_output = null; + } - // Create frame directly - const handle = allocator.create(frame_c.FrameHandle) catch return -1; - errdefer allocator.destroy(handle); + const call_params = Evm.CallParams{ + .caller = native_caller, + .to = native_to, + .value = native_value, + .input = input_data, + .gas_limit = gas_limit, + .is_static = false, + }; - handle.bytecode_owned = allocator.dupe(u8, &bytecode) catch { - allocator.destroy(handle); - return -1; + const result = evm.call(call_params) catch |err| { + state.set_error("EVM call failed: {any}", .{err}); + return null; }; - errdefer allocator.free(handle.bytecode_owned); - handle.interpreter = frame_c.FrameInterpreter.init(allocator, handle.bytecode_owned, 1000000, {}, frame_c.createCApiHost()) catch { - allocator.free(handle.bytecode_owned); - allocator.destroy(handle); - return -1; + // Allocate result structure + const c_result = state.allocator.create(CExecutionResult) catch return null; + + // Copy output data + const output_copy = state.allocator.dupe(u8, result.output) catch { + state.allocator.destroy(c_result); + return null; }; + state.last_result_output = output_copy; - handle.initial_gas = 1000000; - handle.is_stopped = false; + const error_msg = if (result.reverted) "Execution reverted" else ""; - defer { - handle.interpreter.deinit(allocator); - allocator.free(handle.bytecode_owned); - allocator.destroy(handle); + c_result.* = CExecutionResult{ + .success = !result.reverted and result.success, + .gas_used = result.gas_used, + .gas_refunded = result.gas_refunded, + .output_ptr = output_copy.ptr, + .output_len = output_copy.len, + .error_message = error_msg.ptr, + }; + + return c_result; +} + +/// Execute a contract creation (CREATE) +/// Returns execution result (valid until next evm_transact call or evm_destroy) +export fn evm_create_contract( + handle: ?*EvmHandle, + caller: *const CAddress, + value: *const CU256, + init_code: [*]const u8, + init_code_len: usize, + gas_limit: u64, +) ?*const CExecutionResult { + if (handle == null) return null; + const state = CApiState.get_or_init() catch return null; + + const evm: *Evm = @ptrCast(@alignCast(handle)); + + const native_caller = primitives.Address{ .bytes = caller.bytes }; + const native_value = std.mem.readInt(u256, &value.bytes, .little); + const init_code_slice = init_code[0..init_code_len]; + + // Clean up previous result + if (state.last_result_output) |old_output| { + state.allocator.free(old_output); + state.last_result_output = null; } - // Test push/pop operations - handle.interpreter.frame.stack.push(42) catch return -1; - handle.interpreter.frame.stack.push(100) catch return -2; + const create_params = Evm.CallParams{ + .caller = native_caller, + .to = primitives.ZERO_ADDRESS, // CREATE uses zero address + .value = native_value, + .input = init_code_slice, + .gas_limit = gas_limit, + .is_static = false, + }; + + const result = evm.create(create_params) catch |err| { + state.set_error("EVM create failed: {any}", .{err}); + return null; + }; + + // Allocate result structure + const c_result = state.allocator.create(CExecutionResult) catch return null; + + // Copy output data (created contract address) + const output_copy = state.allocator.dupe(u8, result.output) catch { + state.allocator.destroy(c_result); + return null; + }; + state.last_result_output = output_copy; + + const error_msg = if (result.reverted) "Contract creation reverted" else ""; - if (handle.interpreter.frame.stack.size() != 2) return -3; + c_result.* = CExecutionResult{ + .success = !result.reverted and result.success, + .gas_used = result.gas_used, + .gas_refunded = result.gas_refunded, + .output_ptr = output_copy.ptr, + .output_len = output_copy.len, + .error_message = error_msg.ptr, + }; - const value1 = handle.interpreter.frame.stack.pop() catch return -4; - if (value1 != 100) return -5; + return c_result; +} - const value2 = handle.interpreter.frame.stack.pop() catch return -6; - if (value2 != 42) return -7; +/// Destroy an EVM instance and free its resources +export fn evm_destroy(handle: ?*EvmHandle) void { + if (handle == null) return; + const state = CApiState.get_or_init() catch return; - if (handle.interpreter.frame.stack.size() != 0) return -8; + const evm: *Evm = @ptrCast(@alignCast(handle)); + evm.deinit(); + state.allocator.destroy(evm); - return frame_c.EVM_SUCCESS; + // Clean up any remaining result output + if (state.last_result_output) |output| { + state.allocator.free(output); + state.last_result_output = null; + } +} + +/// Cleanup global state (call at program exit) +export fn guillotine_cleanup() void { + CApiState.init_mutex.lock(); + defer CApiState.init_mutex.unlock(); + + if (CApiState.singleton) |state| { + if (state.last_result_output) |output| { + state.allocator.free(output); + } + const allocator = state.allocator; + _ = state.gpa.deinit(); + allocator.destroy(state); + CApiState.singleton = null; + } } // ============================================================================ -// COMPOSITE TESTING FUNCTIONS +// Utility Functions // ============================================================================ -/// Test integration of multiple C API modules -pub export fn evm_test_integration() c_int { - // Create a stack - const stack = stack_c.evm_stack_create() orelse return -1; - defer stack_c.evm_stack_destroy(stack); - - // Create memory - const memory = memory_c.evm_memory_create(0) orelse return -2; - defer memory_c.evm_memory_destroy(memory); - - // Create bytecode - const bytecode_data = [_]u8{ - 0x60, 0x42, // PUSH1 0x42 - 0x60, 0x00, // PUSH1 0x00 - 0x52, // MSTORE - 0x00, // STOP - }; - const bytecode = bytecode_c.evm_bytecode_create(&bytecode_data, bytecode_data.len) orelse return -3; - defer bytecode_c.evm_bytecode_destroy(bytecode); - - // Get bytecode stats instead of analyze - var stats: bytecode_c.CBytecodeStats = undefined; - if (bytecode_c.evm_bytecode_get_stats(bytecode, &stats) != 0) return -4; - - // Execute bytecode operations manually - // PUSH1 0x42 - if (stack_c.evm_stack_push_u64(stack, 0x42) != 0) return -5; - - // PUSH1 0x00 - if (stack_c.evm_stack_push_u64(stack, 0x00) != 0) return -6; - - // MSTORE - pop offset and value - var offset: u64 = 0; - var value: u64 = 0; - if (stack_c.evm_stack_pop_u64(stack, &offset) != 0) return -7; - if (stack_c.evm_stack_pop_u64(stack, &value) != 0) return -8; - - // Write to memory - var value_bytes: [32]u8 = undefined; - @memset(value_bytes[0..31], 0); - value_bytes[31] = @intCast(value); - if (memory_c.evm_memory_write_u256(memory, @intCast(offset), &value_bytes) != 0) return -9; - - // Verify memory contents - var read_bytes: [32]u8 = undefined; - if (memory_c.evm_memory_read_u256(memory, 0, &read_bytes) != 0) return -10; - if (read_bytes[31] != 0x42) return -11; - +/// Helper to create a default block info structure +export fn block_info_create_default() CBlockInfo { + return CBlockInfo{ + .chain_id = 1, + .number = 0, + .parent_hash = [_]u8{0} ** 32, + .timestamp = 0, + .difficulty = 0, + .gas_limit = 30_000_000, + .coinbase = CAddress{ .bytes = [_]u8{0} ** 20 }, + .base_fee = 0, + .prev_randao = [_]u8{0} ** 32, + .blob_base_fee = 0, + }; +} + +/// Helper to create a default transaction context +export fn transaction_context_create_default() CTransactionContext { + return CTransactionContext{ + .gas_limit = 30_000_000, + .coinbase = CAddress{ .bytes = [_]u8{0} ** 20 }, + .chain_id = 1, + .blob_base_fee = CU256{ .bytes = [_]u8{0} ** 32 }, + }; +} + +/// Helper to convert a hex string to an address +/// Returns 0 on success, -1 on failure +export fn address_from_hex(hex: [*:0]const u8, out: *CAddress) c_int { + const hex_str = std.mem.span(hex); + + // Remove "0x" prefix if present + const clean_hex = if (std.mem.startsWith(u8, hex_str, "0x")) + hex_str[2..] + else + hex_str; + + if (clean_hex.len != 40) return -1; + + var i: usize = 0; + while (i < 20) : (i += 1) { + out.bytes[i] = std.fmt.parseInt(u8, clean_hex[i * 2 .. i * 2 + 2], 16) catch return -1; + } + + return 0; +} + +/// Helper to convert a hex string to u256 +/// Returns 0 on success, -1 on failure +export fn u256_from_hex(hex: [*:0]const u8, out: *CU256) c_int { + const hex_str = std.mem.span(hex); + + // Remove "0x" prefix if present + const clean_hex = if (std.mem.startsWith(u8, hex_str, "0x")) + hex_str[2..] + else + hex_str; + + if (clean_hex.len > 64) return -1; + + // Pad with zeros on the left + var padded: [64]u8 = [_]u8{'0'} ** 64; + @memcpy(padded[64 - clean_hex.len ..], clean_hex); + + // Parse as big-endian, store as little-endian + var i: usize = 0; + while (i < 32) : (i += 1) { + const byte_idx = 31 - i; // Reverse for little-endian + out.bytes[byte_idx] = std.fmt.parseInt(u8, padded[i * 2 .. i * 2 + 2], 16) catch return -1; + } + return 0; } + +/// Helper to convert a decimal string to u64 +/// Returns the value, or 0 on failure +export fn u64_from_string(str: [*:0]const u8) u64 { + const str_slice = std.mem.span(str); + return std.fmt.parseInt(u64, str_slice, 10) catch 0; +} From bfcdd989784a0d3821376ff736b118e78e90762c Mon Sep 17 00:00:00 2001 From: William Cory Date: Sun, 5 Oct 2025 00:15:27 -0700 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=8E=A8=20style:=20Fix=20code=20format?= =?UTF-8?q?ting=20in=20C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clean up whitespace inconsistencies and apply consistent formatting throughout evm_c_api.zig. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/evm_c_api.zig | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/evm_c_api.zig b/src/evm_c_api.zig index 3c456099f..a6d1bfbbf 100644 --- a/src/evm_c_api.zig +++ b/src/evm_c_api.zig @@ -1165,11 +1165,11 @@ export fn guillotine_call_tracing(handle: *EvmHandle, params: *const CallParams) setError("FFI not initialized", .{}); return null; }; - + // Set the origin to the caller for top-level transactions // This ensures nonce increments and gas fees are properly handled evm_ptr.origin = primitives.Address{ .bytes = params.caller }; - + const input_slice = if (params.input_len > 0) params.input[0..params.input_len] else &[_]u8{}; const value = std.mem.readInt(u256, ¶ms.value, .big); const call_params = switch (params.call_type) { @@ -1608,12 +1608,12 @@ export fn guillotine_dump_state(handle: *EvmHandle) ?*StateDumpFFI { } accounts_array[idx].address = addr_bytes; - + // Convert balance to bytes var balance_bytes: [32]u8 = undefined; std.mem.writeInt(u256, &balance_bytes, entry.value_ptr.balance, .big); accounts_array[idx].balance = balance_bytes; - + accounts_array[idx].nonce = entry.value_ptr.nonce; // Copy code @@ -1644,7 +1644,7 @@ export fn guillotine_dump_state(handle: *EvmHandle) ?*StateDumpFFI { if (storage_count > 0) { const keys_array = allocator.alloc([32]u8, storage_count) catch { // Clean up - for (accounts_array[0..idx + 1]) |*acc| { + for (accounts_array[0 .. idx + 1]) |*acc| { if (acc.code_len > 0) allocator.free(acc.code[0..acc.code_len]); if (acc.storage_count > 0 and acc != &accounts_array[idx]) { allocator.free(acc.storage_keys[0..acc.storage_count]); @@ -1659,7 +1659,7 @@ export fn guillotine_dump_state(handle: *EvmHandle) ?*StateDumpFFI { const values_array = allocator.alloc([32]u8, storage_count) catch { allocator.free(keys_array); // Clean up - for (accounts_array[0..idx + 1]) |*acc| { + for (accounts_array[0 .. idx + 1]) |*acc| { if (acc.code_len > 0) allocator.free(acc.code[0..acc.code_len]); if (acc.storage_count > 0 and acc != &accounts_array[idx]) { allocator.free(acc.storage_keys[0..acc.storage_count]); @@ -1767,12 +1767,12 @@ export fn guillotine_dump_state_test(handle: *EvmHandle) ?*StateDumpFFI { } accounts_array[idx].address = addr_bytes; - + // Convert balance to bytes var balance_bytes: [32]u8 = undefined; std.mem.writeInt(u256, &balance_bytes, entry.value_ptr.balance, .big); accounts_array[idx].balance = balance_bytes; - + accounts_array[idx].nonce = entry.value_ptr.nonce; // Copy code @@ -1803,7 +1803,7 @@ export fn guillotine_dump_state_test(handle: *EvmHandle) ?*StateDumpFFI { if (storage_count > 0) { const keys_array = allocator.alloc([32]u8, storage_count) catch { // Clean up - for (accounts_array[0..idx + 1]) |*acc| { + for (accounts_array[0 .. idx + 1]) |*acc| { if (acc.code_len > 0) allocator.free(acc.code[0..acc.code_len]); if (acc.storage_count > 0 and acc != &accounts_array[idx]) { allocator.free(acc.storage_keys[0..acc.storage_count]); @@ -1818,7 +1818,7 @@ export fn guillotine_dump_state_test(handle: *EvmHandle) ?*StateDumpFFI { const values_array = allocator.alloc([32]u8, storage_count) catch { allocator.free(keys_array); // Clean up - for (accounts_array[0..idx + 1]) |*acc| { + for (accounts_array[0 .. idx + 1]) |*acc| { if (acc.code_len > 0) allocator.free(acc.code[0..acc.code_len]); if (acc.storage_count > 0 and acc != &accounts_array[idx]) { allocator.free(acc.storage_keys[0..acc.storage_count]);