From ce38f82a7fe91f84493a4edce403281101f1ffcf Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:17:07 +0100 Subject: [PATCH 01/13] upgrade repo to 0.14.0 Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> --- build.zig.zon | 21 +++++++++++---------- src/main.zig | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index e9e66ef..015869d 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,23 +1,24 @@ .{ - .name = "phant", + .fingerprint = 0x24619a25c388c236, + .name = .phant, .version = "0.0.1-beta-0", .dependencies = .{ - .@"zig-rlp" = .{ - .url = "https://github.com/gballet/zig-rlp/archive/refs/tags/v0.1.1-beta7.tar.gz", - .hash = "12201b3645a414dffc2bca872a1467b549d0029b9e36c80fcaeb22a06ec27fab9aee", - }, - .@"zig-eth-secp256k1" = .{ - .url = "https://github.com/jsign/zig-eth-secp256k1/archive/95b7f93.tar.gz", - .hash = "1220c0cf921a5311489bdd6eaf2f2b82bc9245ba39c2da346039aa311e28db633828", + .rlp = .{ + .url = "https://github.com/gballet/zig-rlp/archive/v0.1.1-beta-8.tar.gz", + .hash = "rlp-0.1.0-7i0JtBGSAACU44qijBzojQzyn2kLZ-wnIn5qOJxpWnky", }, .httpz = .{ - .url = "https://github.com/karlseguin/http.zig/archive/c224ebb.tar.gz", - .hash = "12206297df84406cd4eed306acef30eb2a1a6a18b6771ebb4920391a3a7299b2e6a7", + .url = "https://github.com/karlseguin/http.zig/archive/46753ab.tar.gz", + .hash = "12207dbe64a04fb960156cbc990153cb3637a08e3fe23077c7199621b5c6377f5d20", }, .zigcli = .{ .url = "https://github.com/jiacai2050/zigcli/archive/54d095c.tar.gz", .hash = "1220e8fb37224ab6ee9c575129594a808643b548596d63dd8b87cb42e22a7eed9f51", }, + .zig_eth_secp256k1 = .{ + .url = "https://github.com/gballet/zig-eth-secp256k1/archive/upgrade-to-0.14.0.tar.gz", + .hash = "zig_eth_secp256k1-0.1.0-_U97sGzoDAAuzO5WEYqKKaGiJAmTNq99mPCh0JdsNWY3", + }, }, .paths = .{""}, } diff --git a/src/main.zig b/src/main.zig index a1c6bf7..c6cb346 100644 --- a/src/main.zig +++ b/src/main.zig @@ -140,7 +140,7 @@ pub fn main() !void { }; var blockchain = try Blockchain.init(allocator, config.chainId, &statedb, parent_header, try Fork.frontier.newFrontierFork(allocator)); - var engine_api_server = try httpz.ServerApp(*Blockchain).init(allocator, .{ + var engine_api_server = try httpz.Server(*Blockchain).init(allocator, .{ .port = port, }, &blockchain); var router = engine_api_server.router(); From 45e925750f01e13075ad9484fa62cf7523961b75 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:17:07 +0100 Subject: [PATCH 02/13] upgrade repo to 0.14.0 Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> --- build.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.zig b/build.zig index 4bcf22f..06f98c7 100644 --- a/build.zig +++ b/build.zig @@ -67,8 +67,8 @@ pub fn build(b: *std.Build) !void { // set a preferred release mode, allowing the user to decide how to optimize. const optimize = b.standardOptimizeOption(.{}); - const dep_rlp = b.dependency("zig-rlp", .{ .target = target, .optimize = optimize }); - const depSecp256k1 = b.dependency("zig-eth-secp256k1", .{ .target = target, .optimize = optimize }); + const dep_rlp = b.dependency("rlp", .{ .target = target, .optimize = optimize }); + const depSecp256k1 = b.dependency("zig_eth_secp256k1", .{ .target = target, .optimize = optimize }); const mod_secp256k1 = depSecp256k1.module("zig-eth-secp256k1"); const httpz = b.dependency("httpz", .{ .target = target, From 2d25b13410682b19654e3b957440c46eb8da3dd7 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Sat, 10 May 2025 15:24:33 +0200 Subject: [PATCH 03/13] build evmone Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> --- build.zig | 90 +++++++------------------------------------------------ 1 file changed, 11 insertions(+), 79 deletions(-) diff --git a/build.zig b/build.zig index 06f98c7..89ca1a7 100644 --- a/build.zig +++ b/build.zig @@ -76,63 +76,9 @@ pub fn build(b: *std.Build) !void { }); const mod_httpz = httpz.module("httpz"); - const ethash = b.addStaticLibrary(.{ - .name = "ethash", - .optimize = optimize, - .target = target, - }); - const cflags = [_][]const u8{ - "-Wall", "-O3", "-fvisibility=hidden", - "-fvisibility-inlines-hidden", "-Wpedantic", "-Werror", - "-Wextra", "-Wshadow", "-Wconversion", - "-Wsign-conversion", "-Wno-unknown-pragmas", "-fno-stack-protector", - "-Wimplicit-fallthrough", "-Wmissing-declarations", "-Wno-attributes", - "-Wextra-semi", "-fno-exceptions", "-fno-rtti", - "-Wno-deprecated", // this one is used to remove a warning about char_trait deprecation - "-Wno-strict-prototypes", // this one is used by glue.c to avoid a warning that does not disappear when the prototype is added. - }; - ethash.addCSourceFiles(.{ .root = b.path(""), .files = &[_][]const u8{"ethash/lib/keccak/keccak.c"}, .flags = &cflags }); - ethash.addIncludePath(b.path("ethash/include")); - ethash.linkLibC(); - ethash.linkLibCpp(); - b.installArtifact(ethash); - - const evmone = b.addStaticLibrary(.{ - .name = "evmone", - .optimize = optimize, - .target = target, - }); - const cppflags = [_][]const u8{ - "-Wall", "-std=c++20", "-O3", - "-fvisibility=hidden", "-fvisibility-inlines-hidden", "-Wpedantic", - "-Werror", "-Wextra", "-Wshadow", - "-Wconversion", "-Wsign-conversion", "-Wno-unknown-pragmas", - "-fno-stack-protector", "-Wimplicit-fallthrough", "-Wmissing-declarations", - "-Wno-attributes", "-Wextra-semi", "-fno-exceptions", - "-fno-rtti", - "-Wno-deprecated", // this one is used to remove a warning about char_trait deprecation - "-DPROJECT_VERSION=\"0.14.0-dev\"", - }; - evmone.addCSourceFiles(.{ .root = b.path(""), .files = &[_][]const u8{ - "evmone/lib/evmone/advanced_analysis.cpp", - "evmone/lib/evmone/eof.cpp", - "evmone/lib/evmone/advanced_execution.cpp", - "evmone/lib/evmone/instructions_calls.cpp", - "evmone/lib/evmone/advanced_instructions.cpp", - "evmone/lib/evmone/instructions_storage.cpp", - "evmone/lib/evmone/baseline.cpp", - "evmone/lib/evmone/tracing.cpp", - "evmone/lib/evmone/baseline_instruction_table.cpp", - "evmone/lib/evmone/vm.cpp", - }, .flags = &cppflags }); - - evmone.addIncludePath(b.path("evmone/evmc/include")); - evmone.addIncludePath(b.path("evmone/include")); - evmone.addIncludePath(b.path("intx/include")); - evmone.addIncludePath(b.path("ethash/include")); - evmone.linkLibC(); - evmone.linkLibCpp(); - b.installArtifact(evmone); + const evmone_cmake_config_step = b.addSystemCommand(&.{ "cmake", "-S", "evmone", "-B", "zig-out/evmone_build" }); + const evmone_cmake_build_step = b.addSystemCommand(&.{ "cmake", "--build", "zig-out/evmone_build" }); + evmone_cmake_build_step.step.dependOn(&evmone_cmake_config_step.step); const zigcli = b.dependency("zigcli", .{}); @@ -146,16 +92,9 @@ pub fn build(b: *std.Build) !void { }); exe.addIncludePath(b.path("evmone/include/evmone")); exe.addIncludePath(b.path("evmone/evmc/include")); - if (target.result.cpu.arch == .x86_64) { - // On x86_64, some functions are missing from the static library, - // so we define dummy functions to make sure that it compiles. - exe.addCSourceFile(.{ - .file = b.path("src/glue.c"), - .flags = &cflags, - }); - } - exe.linkLibrary(ethash); - exe.linkLibrary(evmone); + exe.addLibraryPath(b.path("zig-out/evmone_build/lib")); + exe.linkSystemLibrary("evmone"); + // exe.linkCxxAbi(); exe.linkLibC(); exe.root_module.addImport("zig-rlp", dep_rlp.module("zig-rlp")); exe.linkLibrary(depSecp256k1.artifact("secp256k1")); @@ -163,6 +102,7 @@ pub fn build(b: *std.Build) !void { exe.root_module.addImport("httpz", mod_httpz); exe.root_module.addImport("simargs", zigcli.module("simargs")); exe.root_module.addImport("pretty-table", zigcli.module("pretty-table")); + exe.step.dependOn(&evmone_cmake_build_step.step); // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default @@ -199,18 +139,10 @@ pub fn build(b: *std.Build) !void { .target = target, .optimize = optimize, }); - unit_tests.addIncludePath(b.path("evmone/include/evmone")); - unit_tests.addIncludePath(b.path("evmone/evmc/include")); - if (target.result.cpu.arch == .x86_64) { - // On x86_64, some functions are missing from the static library, - // so we define dummy functions to make sure that it compiles. - unit_tests.addCSourceFile(.{ - .file = b.path("src/glue.c"), - .flags = &cflags, - }); - } - unit_tests.linkLibrary(ethash); - unit_tests.linkLibrary(evmone); + exe.addLibraryPath(b.path("zig-out/evmone_build/lib")); + exe.linkSystemLibrary("evmone"); + // exe.linkCxxAbi(); + exe.linkLibC(); unit_tests.linkLibC(); unit_tests.root_module.addImport("zig-rlp", dep_rlp.module("zig-rlp")); unit_tests.linkLibrary(depSecp256k1.artifact("secp256k1")); From e6b72badbc8ec4bbcc53069d477953ab2b564db8 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Sat, 10 May 2025 18:07:20 +0200 Subject: [PATCH 04/13] upgrade ci --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2dc66ce..cef231d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Zig uses: korandoru/setup-zig@v1 with: - zig-version: 0.13.0 + zig-version: 0.14.0 - name: Build run: git submodule update --init --recursive && zig build @@ -29,7 +29,7 @@ jobs: - name: Set up Zig uses: korandoru/setup-zig@v1 with: - zig-version: 0.13.0 + zig-version: 0.14.0 - name: Lint run: zig fmt --check src/*.zig @@ -42,7 +42,7 @@ jobs: - name: Set up Zig uses: korandoru/setup-zig@v1 with: - zig-version: 0.13.0 + zig-version: 0.14.0 - name: Test run: git submodule update --init --recursive && zig build test @@ -55,7 +55,7 @@ jobs: - name: Set up Zig uses: korandoru/setup-zig@v1 with: - zig-version: 0.13.0 + zig-version: 0.14.0 - name: Test run: git submodule update --init --recursive && zig build -Dtarget="aarch64-linux" From b703ab35dd72f146e8a2c396f47032f4f21690a4 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Sat, 10 May 2025 18:07:51 +0200 Subject: [PATCH 05/13] get library as an external component Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> --- build.zig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/build.zig b/build.zig index 89ca1a7..c382f21 100644 --- a/build.zig +++ b/build.zig @@ -139,11 +139,13 @@ pub fn build(b: *std.Build) !void { .target = target, .optimize = optimize, }); - exe.addLibraryPath(b.path("zig-out/evmone_build/lib")); - exe.linkSystemLibrary("evmone"); + unit_tests.addLibraryPath(b.path("zig-out/evmone_build/lib")); + unit_tests.linkSystemLibrary("evmone"); // exe.linkCxxAbi(); - exe.linkLibC(); unit_tests.linkLibC(); + unit_tests.linkLibC(); + unit_tests.addIncludePath(b.path("evmone/include/evmone")); + unit_tests.addIncludePath(b.path("evmone/evmc/include")); unit_tests.root_module.addImport("zig-rlp", dep_rlp.module("zig-rlp")); unit_tests.linkLibrary(depSecp256k1.artifact("secp256k1")); unit_tests.root_module.addImport("zig-eth-secp256k1", mod_secp256k1); From 0133456b18b7f6b81a945376a1f1c2eee1bf6ad6 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Sat, 10 May 2025 18:08:06 +0200 Subject: [PATCH 06/13] update dependencies and fix issues Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> --- README.md | 2 +- build.zig.zon | 8 ++++---- src/blockchain/vm.zig | 2 +- src/main.zig | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5ba54af..a540152 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ An experimental Zig Ethereum client. This repo is very experimental, so you have to do some things once. -We use the [Zig v0.13](https://ziglang.org/download/) compiler version. +We use the [Zig v0.14.0](https://ziglang.org/download/) compiler version. ### Initialize git submodules diff --git a/build.zig.zon b/build.zig.zon index 015869d..9b9951e 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -8,12 +8,12 @@ .hash = "rlp-0.1.0-7i0JtBGSAACU44qijBzojQzyn2kLZ-wnIn5qOJxpWnky", }, .httpz = .{ - .url = "https://github.com/karlseguin/http.zig/archive/46753ab.tar.gz", - .hash = "12207dbe64a04fb960156cbc990153cb3637a08e3fe23077c7199621b5c6377f5d20", + .url = "git+https://github.com/karlseguin/http.zig?ref=master#9434eaa607f855da77b80387b0be7f7cf2aaaa63", + .hash = "httpz-0.0.0-PNVzrFy3BgC7H3cLoiB1odzkl37NJdPx-OHG6eRXeQLX", }, .zigcli = .{ - .url = "https://github.com/jiacai2050/zigcli/archive/54d095c.tar.gz", - .hash = "1220e8fb37224ab6ee9c575129594a808643b548596d63dd8b87cb42e22a7eed9f51", + .url = "https://github.com/jiacai2050/zigcli/archive/refs/tags/v0.2.0.tar.gz", + .hash = "zigcli-0.2.0-ORC7jKlzAgDYhWCmfjBE9jWPA7CqMQKlMTT3Rcadfgzv", }, .zig_eth_secp256k1 = .{ .url = "https://github.com/gballet/zig-eth-secp256k1/archive/upgrade-to-0.14.0.tar.gz", diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 8f217c8..8b4b1aa 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -525,7 +525,7 @@ const EVMOneHost = struct { // toEVMCAddress transforms an Address or ?Address into an evmc_address. fn toEVMCAddress(address: anytype) evmc.struct_evmc_address { const addr_typeinfo = @typeInfo(@TypeOf(address)); - if (@TypeOf(address) != Address and addr_typeinfo.Optional.child != Address) { + if (@TypeOf(address) != Address and addr_typeinfo.optional.child != Address) { @compileError("address must be of type Address or ?Address"); } diff --git a/src/main.zig b/src/main.zig index c6cb346..6fde5dc 100644 --- a/src/main.zig +++ b/src/main.zig @@ -143,8 +143,8 @@ pub fn main() !void { var engine_api_server = try httpz.Server(*Blockchain).init(allocator, .{ .port = port, }, &blockchain); - var router = engine_api_server.router(); - router.post("/", engineAPIHandler); + var router = try engine_api_server.router(.{}); + router.post("/", engineAPIHandler, .{}); std.log.info("Listening on {}", .{port}); try engine_api_server.listen(); } From 99bc873179cdeb381c6a783b58880af62bd57c6e Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 28 May 2025 16:53:40 +0200 Subject: [PATCH 07/13] use zevem in a freestanding context Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> --- build.zig | 60 ++- build.zig.zon | 8 +- src/blockchain/blockchain.zig | 9 +- src/blockchain/fork.zig | 2 +- src/blockchain/forks/frontier.zig | 4 +- src/blockchain/forks/prague.zig | 4 +- src/blockchain/types.zig | 11 +- src/blockchain/{vm.zig => vm_evmc.zig} | 10 +- src/blockchain/vm_zevem.zig | 503 +++++++++++++++++++++++++ src/common/common.zig | 3 +- src/common/contract.zig | 3 +- src/engine_api/engine_api.zig | 6 +- src/engine_api/execution_payload.zig | 6 +- src/lib.zig | 18 +- src/main.zig | 18 +- src/tests/custom_tests.zig | 15 +- src/tests/lib_tests.zig | 11 + src/tests/spec_tests.zig | 17 +- 18 files changed, 628 insertions(+), 80 deletions(-) rename src/blockchain/{vm.zig => vm_evmc.zig} (99%) create mode 100644 src/blockchain/vm_zevem.zig create mode 100644 src/tests/lib_tests.zig diff --git a/build.zig b/build.zig index c382f21..83af9a4 100644 --- a/build.zig +++ b/build.zig @@ -82,6 +82,20 @@ pub fn build(b: *std.Build) !void { const zigcli = b.dependency("zigcli", .{}); + const lib_mod = b.createModule(.{ + .root_source_file = b.path("src/lib.zig"), + .optimize = optimize, + .target = target, + }); + + const lib = b.addLibrary(.{ + .name = "phant", + .root_module = lib_mod, + }); + // add itself as an import to solve a dependency cycle in tests + lib.root_module.addImport("lib", lib.root_module); + b.installArtifact(lib); + const exe = b.addExecutable(.{ .name = "phant", // In this case the main source file is merely a path, however, in more @@ -90,19 +104,38 @@ pub fn build(b: *std.Build) !void { .target = target, .optimize = optimize, }); - exe.addIncludePath(b.path("evmone/include/evmone")); - exe.addIncludePath(b.path("evmone/evmc/include")); - exe.addLibraryPath(b.path("zig-out/evmone_build/lib")); - exe.linkSystemLibrary("evmone"); - // exe.linkCxxAbi(); + + const vm_mod = if (target.result.os.tag == .freestanding) vm_mod: { + break :vm_mod b.createModule(.{ + .root_source_file = b.path("src/blockchain/vm_zevem.zig"), + .target = target, + .optimize = optimize, + }); + } else vm_mod: { + const vm_mod = b.createModule(.{ + .root_source_file = b.path("src/blockchain/vm_evmc.zig"), + .target = target, + .optimize = optimize, + }); + vm_mod.addIncludePath(b.path("evmone/include/evmone")); + vm_mod.addIncludePath(b.path("evmone/evmc/include")); + vm_mod.addLibraryPath(b.path("zig-out/evmone_build/lib")); + lib.linkSystemLibrary("evmone"); + lib.step.dependOn(&evmone_cmake_build_step.step); + exe.step.dependOn(&evmone_cmake_build_step.step); + break :vm_mod vm_mod; + }; + vm_mod.addImport("lib", lib_mod); + lib_mod.addImport("vm", vm_mod); exe.linkLibC(); - exe.root_module.addImport("zig-rlp", dep_rlp.module("zig-rlp")); + exe.linkLibrary(lib); + lib_mod.addImport("zig-rlp", dep_rlp.module("zig-rlp")); exe.linkLibrary(depSecp256k1.artifact("secp256k1")); - exe.root_module.addImport("zig-eth-secp256k1", mod_secp256k1); + lib_mod.addImport("zig-eth-secp256k1", mod_secp256k1); exe.root_module.addImport("httpz", mod_httpz); exe.root_module.addImport("simargs", zigcli.module("simargs")); - exe.root_module.addImport("pretty-table", zigcli.module("pretty-table")); - exe.step.dependOn(&evmone_cmake_build_step.step); + lib_mod.addImport("pretty-table", zigcli.module("pretty-table")); + exe.root_module.addImport("lib", lib_mod); // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default @@ -135,21 +168,20 @@ pub fn build(b: *std.Build) !void { // Creates a step for unit testing. This only builds the test executable // but does not run it. const unit_tests = b.addTest(.{ - .root_source_file = b.path("src/lib.zig"), + .root_source_file = b.path("src/tests/lib_tests.zig"), .target = target, .optimize = optimize, }); + unit_tests.root_module.addImport("lib", lib_mod); unit_tests.addLibraryPath(b.path("zig-out/evmone_build/lib")); unit_tests.linkSystemLibrary("evmone"); - // exe.linkCxxAbi(); - unit_tests.linkLibC(); unit_tests.linkLibC(); - unit_tests.addIncludePath(b.path("evmone/include/evmone")); - unit_tests.addIncludePath(b.path("evmone/evmc/include")); + unit_tests.root_module.addImport("vm", vm_mod); unit_tests.root_module.addImport("zig-rlp", dep_rlp.module("zig-rlp")); unit_tests.linkLibrary(depSecp256k1.artifact("secp256k1")); unit_tests.root_module.addImport("zig-eth-secp256k1", mod_secp256k1); unit_tests.root_module.addImport("pretty-table", zigcli.module("pretty-table")); + unit_tests.step.dependOn(&evmone_cmake_build_step.step); const run_unit_tests = b.addRunArtifact(unit_tests); run_unit_tests.has_side_effects = true; diff --git a/build.zig.zon b/build.zig.zon index 9b9951e..576d23a 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -16,8 +16,12 @@ .hash = "zigcli-0.2.0-ORC7jKlzAgDYhWCmfjBE9jWPA7CqMQKlMTT3Rcadfgzv", }, .zig_eth_secp256k1 = .{ - .url = "https://github.com/gballet/zig-eth-secp256k1/archive/upgrade-to-0.14.0.tar.gz", - .hash = "zig_eth_secp256k1-0.1.0-_U97sGzoDAAuzO5WEYqKKaGiJAmTNq99mPCh0JdsNWY3", + .url = "git+https://github.com/gballet/zig-eth-secp256k1?ref=add-freestanding-support#4e7e77f50bd0e852fb6a824b9c2ac2c4f8c91bf9", + .hash = "zig_eth_secp256k1-0.1.0-_U97sBzsDACmwsVCfA9UA3UPr8pK8qaSeLHx97Q_Nnzu", + }, + .zevem = .{ + .url = "https://github.com/tsujp/zevem/archive/3fab1c1.tar.gz", + .hash = "zevem-0.0.0-ur64bt16AQDrmtK17yXjv2QTRQNxHY1ZFs9KLPrO_Vyq", }, }, .paths = .{""}, diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index 492ec43..0e70b1b 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -4,11 +4,12 @@ const common = @import("../common/common.zig"); const blocks = @import("../types/block.zig"); const config = @import("../config/config.zig"); const transaction = @import("../types/transaction.zig"); -const vm = @import("vm.zig"); +const vm = @import("vm"); const rlp = @import("zig-rlp"); const signer = @import("../signer/signer.zig"); -const params = @import("params.zig"); -const blockchain_types = @import("types.zig"); +pub const params = @import("params.zig"); +const lib = @import("lib"); +const blockchain_types = lib.blockchain_types; const mpt = @import("../mpt/mpt.zig"); const Allocator = std.mem.Allocator; const AddressSet = common.AddressSet; @@ -20,7 +21,7 @@ const Tx = types.Tx; pub const BlockHeader = types.BlockHeader; const Environment = blockchain_types.Environment; const Message = blockchain_types.Message; -const StateDB = @import("../state/state.zig").StateDB; +const StateDB = lib.state.StateDB; const Hash32 = types.Hash32; const Bytes32 = types.Bytes32; const Address = types.Address; diff --git a/src/blockchain/fork.zig b/src/blockchain/fork.zig index af91e13..5137c4a 100644 --- a/src/blockchain/fork.zig +++ b/src/blockchain/fork.zig @@ -1,4 +1,4 @@ -const lib = @import("../lib.zig"); +const lib = @import("lib"); const Hash32 = lib.types.Hash32; const Fork = @This(); pub const frontier = @import("./forks/frontier.zig"); diff --git a/src/blockchain/forks/frontier.zig b/src/blockchain/forks/frontier.zig index 2ee0524..f476a59 100644 --- a/src/blockchain/forks/frontier.zig +++ b/src/blockchain/forks/frontier.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const Fork = @import("../fork.zig"); -const lib = @import("../../lib.zig"); +const lib = @import("lib"); +const Fork = lib.blockchain.Fork; const Hash32 = lib.types.Hash32; const base_fork_vtable = Fork.VTable{ diff --git a/src/blockchain/forks/prague.zig b/src/blockchain/forks/prague.zig index 612d1b5..ef3c03e 100644 --- a/src/blockchain/forks/prague.zig +++ b/src/blockchain/forks/prague.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const Fork = @import("../fork.zig"); -const lib = @import("../../lib.zig"); +const lib = @import("lib"); +const Fork = lib.fork; const Hash32 = lib.types.Hash32; const StateDB = lib.state.StateDB; const Address = lib.types.Address; diff --git a/src/blockchain/types.zig b/src/blockchain/types.zig index 8c24aa1..1490413 100644 --- a/src/blockchain/types.zig +++ b/src/blockchain/types.zig @@ -1,8 +1,9 @@ -const types = @import("../types/types.zig"); -const config = @import("../config/config.zig"); -const common = @import("../common/common.zig"); -const Fork = @import("fork.zig"); -const StateDB = @import("../state/state.zig").StateDB; +const lib = @import("lib"); +const types = lib.types; +const config = lib.config; +const common = lib.common; +const Fork = lib.blockchain.Fork; +const StateDB = lib.state.StateDB; const Address = types.Address; const Hash32 = types.Hash32; const Bytes32 = types.Bytes32; diff --git a/src/blockchain/vm.zig b/src/blockchain/vm_evmc.zig similarity index 99% rename from src/blockchain/vm.zig rename to src/blockchain/vm_evmc.zig index 8b4b1aa..e650b44 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm_evmc.zig @@ -1,11 +1,13 @@ const evmc = @cImport({ @cInclude("evmone.h"); }); +const EVM = @import("zevem").EVM; const std = @import("std"); -const types = @import("../types/types.zig"); -const common = @import("../common/common.zig"); -const params = @import("params.zig"); -const blockchain_types = @import("types.zig"); +const lib = @import("lib"); +const types = lib.types; +const common = lib.common; +const params = lib.blockchain.params; +const blockchain_types = lib.blockchain_types; const Allocator = std.mem.Allocator; const AddressSet = common.AddressSet; const AddressKey = common.AddressKey; diff --git a/src/blockchain/vm_zevem.zig b/src/blockchain/vm_zevem.zig new file mode 100644 index 0000000..e2d3805 --- /dev/null +++ b/src/blockchain/vm_zevem.zig @@ -0,0 +1,503 @@ +const zevem = zevem.EVM; +const EVM = zevem.EVM; +const std = @import("std"); +const types = @import("../types/types.zig"); +const common = @import("../common/common.zig"); +const params = @import("params.zig"); +const blockchain_types = @import("types.zig"); +const Allocator = std.mem.Allocator; +const AddressSet = common.AddressSet; +const AddressKey = common.AddressKey; +const AddressKeySet = common.AddressKeySet; +const Environment = blockchain_types.Environment; +const Message = blockchain_types.Message; +const Block = types.Block; +const Hash32 = types.Hash32; +const Address = types.Address; +const Keccak256 = std.crypto.hash.sha3.Keccak256; +const fmtSliceHexLower = std.fmt.fmtSliceHexLower; +const assert = std.debug.assert; + +const empty_hash = common.comptimeHexToBytes("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); + +const EnvFuncs = struct {}; + +pub const VM = struct { + const vmlog = std.log.scoped(.vm); + + allocator: Allocator, + env: Environment, + evm: EVM, + + // init creates a new EVM VM instance. The caller must call deinit() when done. + pub fn init(allocator: Allocator, env: Environment) VM { + const evm = EVM.initi(EnvFuncs{}); + return .{ + .allocator = allocator, + .env = env, + .evm = evm, + }; + } + + // deinit destroys a VM instance. + pub fn deinit(self: *VM) void { + if (self.evm.*.destroy) |destroy| { + destroy(self.evm); + } + } + + // processMessageCall executes a message call. + pub fn processMessageCall(self: *VM, msg: Message) !MessageCallOutput { + if (msg.target) { + try self.env.state.incrementNonce(msg.sender); + } + + const result = self.evm.execute(); + return .{ + .gas_left = @intCast(result.gas_left), + .refund_counter = @intCast(result.gas_refund), + .success = result.status_code == evmc.EVMC_SUCCESS, + }; + } +}; + +// EVMOneHost contains the implementation of the EVMC host interface. +// https://evmc.ethereum.org/structevmc__host__interface.html +const EVMOneHost = struct { + const evmclog = std.log.scoped(.evmone); + + fn get_tx_context(ctx: ?*evmc.struct_evmc_host_context) callconv(.C) evmc.struct_evmc_tx_context { + evmclog.debug("getTxContext", .{}); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + return evmc.struct_evmc_tx_context{ + .tx_gas_price = toEVMCUint256Be(vm.env.gas_price), + .tx_origin = toEVMCAddress(vm.env.origin), + .block_coinbase = toEVMCAddress(vm.env.coinbase), + .block_number = @intCast(vm.env.number), + .block_timestamp = @intCast(vm.env.time), + .block_gas_limit = @intCast(vm.env.gas_limit), + .block_prev_randao = .{ .bytes = vm.env.prev_randao }, + .chain_id = toEVMCUint256Be(@intFromEnum(vm.env.chain_id)), + .block_base_fee = toEVMCUint256Be(vm.env.base_fee_per_gas), + }; + } + + fn get_block_hash(ctx: ?*evmc.struct_evmc_host_context, block_number: i64) callconv(.C) evmc.evmc_bytes32 { + evmclog.debug("getBlockHash block_number={}", .{block_number}); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + const idx = vm.env.number - @as(u64, @intCast(block_number)); + return .{ .bytes = vm.env.fork.get_parent_block_hash(idx) catch @panic("unhandled error getting parent hash") }; + } + + fn account_exists(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) bool { + const address = fromEVMCAddress(addr.*); + evmclog.debug("accountExists addr=0x{}", .{fmtSliceHexLower(&address)}); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + + return vm.env.state.getAccountOpt(address) != null; + } + + fn get_storage( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + key: [*c]const evmc.evmc_bytes32, + ) callconv(.C) evmc.evmc_bytes32 { + const address = fromEVMCAddress(addr.*); + evmclog.debug("getStorage addr=0x{} key={}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&key.*.bytes) }); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + const k = std.mem.readInt(u256, &key.*.bytes, std.builtin.Endian.big); + + return .{ .bytes = vm.env.state.getStorage(address, k) }; + } + + fn set_storage( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + key: [*c]const evmc.evmc_bytes32, + value: [*c]const evmc.evmc_bytes32, + ) callconv(.C) evmc.enum_evmc_storage_status { + const address = fromEVMCAddress(addr.*); + evmclog.debug("setStorage addr=0x{} key={} value={}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&key.*.bytes), fmtSliceHexLower(&value.*.bytes) }); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + + const k = std.mem.readInt(u256, &key.*.bytes, std.builtin.Endian.big); + const storage_status: evmc.enum_evmc_storage_status = blk: { + const original_value = vm.env.state.getOriginalStorage(address, k); + const current_value = vm.env.state.getStorage(address, k); + const new_value = value.*.bytes; + const zero = std.mem.zeroes([32]u8); + + // See: https://evmc.ethereum.org/group__EVMC.html#gae012fd6b8e5c23806b507c2d3e9fb1aa + + // EIP-220: 2. + if (std.mem.eql(u8, ¤t_value, &new_value)) { + break :blk evmc.EVMC_STORAGE_ASSIGNED; + } + // EIP-220: 3. + + // EIP-220: 3.1 + if (std.mem.eql(u8, &original_value, ¤t_value)) { + // EIP-220: 3.1.1 + if (std.mem.eql(u8, &original_value, &zero)) { + // 0->0->Z + break :blk evmc.EVMC_STORAGE_ADDED; + } + if (std.mem.eql(u8, &new_value, &zero)) { + // X->X->0 + break :blk evmc.EVMC_STORAGE_DELETED; + } + // X->X->Z + break :blk evmc.EVMC_STORAGE_MODIFIED; + } + + // EIP-220: 3.2 + // X != Y + + // EIP-220: 3.2.1 + if (!std.mem.eql(u8, &original_value, &zero)) { + // EIP-220: 3.2.1.1 + if (std.mem.eql(u8, ¤t_value, &zero)) { + // X->0->Z + break :blk evmc.EVMC_STORAGE_DELETED_ADDED; + } + // EIP-220: 3.2.1.2 + if (std.mem.eql(u8, &new_value, &zero)) { + // X->Y->0 + break :blk evmc.EVMC_STORAGE_MODIFIED_DELETED; + } + } + + // EIP-220: 3.2.2 + if (std.mem.eql(u8, &original_value, &new_value)) { + if (std.mem.eql(u8, ¤t_value, &zero)) { + // X->0->X + break :blk evmc.EVMC_STORAGE_DELETED_RESTORED; + } + // EIP-220: 3.2.2.1 + if (std.mem.eql(u8, &original_value, &zero)) { + // 0->Y->0 + break :blk evmc.EVMC_STORAGE_ADDED_DELETED; + } + // X->Y->X + break :blk evmc.EVMC_STORAGE_MODIFIED_RESTORED; + } + + break :blk evmc.EVMC_STORAGE_ASSIGNED; + }; + + vm.env.state.setStorage(address, k, value.*.bytes) catch |err| switch (err) { + // From EVMC docs: "The VM MUST make sure that the account exists. This requirement is only a formality + // because VM implementations only modify storage of the account of the current execution context". + error.AccountDoesNotExist => @panic("set storage in non-existent account"), + error.OutOfMemory => @panic("OOO"), + }; + + return storage_status; + } + + fn get_balance(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) evmc.evmc_uint256be { + const address = fromEVMCAddress(addr.*); + evmclog.debug("getBalance addr=0x{})", .{fmtSliceHexLower(&address)}); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + + return toEVMCUint256Be(vm.env.state.getAccount(address).balance); + } + + fn get_code_size(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) usize { + const address = fromEVMCAddress(addr.*); + evmclog.debug("getCodeSize addr=0x{})", .{fmtSliceHexLower(&address)}); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + + return vm.env.state.getAccount(address).code.len; + } + + fn get_code_hash( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + ) callconv(.C) evmc.evmc_bytes32 { + const address = fromEVMCAddress(addr.*); + evmclog.debug("getCodeHash addr=0x{})", .{fmtSliceHexLower(&address)}); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + var ret = empty_hash; + const code = vm.env.state.getAccount(address).code; + if (code.len > 0) + Keccak256.hash(code, &ret, .{}); + + return .{ .bytes = ret }; + } + + fn copy_code( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + code_offset: usize, + buffer_data: [*c]u8, + buffer_size: usize, + ) callconv(.C) usize { + const address = fromEVMCAddress(addr.*); + evmclog.debug("copyCode addr=0x{} code_offset={})", .{ fmtSliceHexLower(&address), code_offset }); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + const code = vm.env.state.getAccount(address).code; + + const copy_len = @min(buffer_size, code.len - code_offset); + @memcpy(buffer_data[0..copy_len], code[code_offset..][0..copy_len]); + + return copy_len; + } + + fn self_destruct( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + addr2: [*c]const evmc.evmc_address, + ) callconv(.C) bool { + _ = addr2; + _ = addr; + _ = ctx; + // https://evmc.ethereum.org/group__EVMC.html#ga1aa9fa657b3f0de375e2f07e53b65bcc + @panic("TODO"); + } + + fn emit_log( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + data: [*c]const u8, + data_size: usize, + topics: [*c]const evmc.evmc_bytes32, + topics_count: usize, + ) callconv(.C) void { + _ = topics_count; + _ = topics; + _ = data_size; + _ = data; + _ = addr; + _ = ctx; + // https://evmc.ethereum.org/group__EVMC.html#gaab96621b67d653758b3da15c2b596938 + @panic("TODO"); + } + + fn access_account(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) evmc.enum_evmc_access_status { + const address = fromEVMCAddress(addr.*); + evmclog.debug("accessAccount addr=0x{}", .{fmtSliceHexLower(&address)}); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + if (vm.env.state.accessedAccountsContains(address)) + return evmc.EVMC_ACCESS_WARM; + vm.env.state.putAccessedAccount(address) catch |err| switch (err) { + error.OutOfMemory => @panic("OOO"), + }; + + return evmc.EVMC_ACCESS_COLD; + } + + fn access_storage( + ctx: ?*evmc.struct_evmc_host_context, + addr: [*c]const evmc.evmc_address, + key: [*c]const evmc.evmc_bytes32, + ) callconv(.C) evmc.enum_evmc_access_status { + const address = fromEVMCAddress(addr.*); + evmclog.debug("accessStorage addr=0x{} key=0x{}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&key.*.bytes) }); + + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + const address_key: AddressKey = .{ .address = address, .key = key.*.bytes }; + if (vm.env.state.accessedStorageKeysContains(address_key)) + return evmc.EVMC_ACCESS_WARM; + _ = vm.env.state.putAccessedStorageKeys(address_key) catch |err| switch (err) { + error.OutOfMemory => @panic("OOO"), + }; + + return evmc.EVMC_ACCESS_COLD; + } + + fn call(ctx: ?*evmc.struct_evmc_host_context, _msg: [*c]const evmc.struct_evmc_message) callconv(.C) evmc.struct_evmc_result { + const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + + var msg = _msg.*; + + const code = switch (msg.kind) { + evmc.EVMC_CALL, + evmc.EVMC_DELEGATECALL, + evmc.EVMC_CALLCODE, + => vm.env.state.getAccount(fromEVMCAddress(msg.code_address)).code, + evmc.EVMC_CREATE, + evmc.EVMC_CREATE2, + => if (msg.input_size == 0) &[_]u8{} else msg.input_data[0..msg.input_size], + else => @panic("unknown message kind"), + }; + + const sender = fromEVMCAddress(msg.sender); + const recipient = fromEVMCAddress(msg.recipient); + + msg.recipient = switch (msg.kind) { + evmc.EVMC_CREATE => blk: { + const sender_nonce: u64 = @intCast(vm.env.state.getAccount(sender).nonce); + break :blk .{ .bytes = common.computeCREATEContractAddress(vm.allocator, sender, sender_nonce) catch unreachable }; + }, + evmc.EVMC_CREATE2 => .{ .bytes = common.computeCREATE2ContractAddress(sender, msg.create2_salt.bytes, code) catch unreachable }, + evmc.EVMC_CALL, + evmc.EVMC_DELEGATECALL, + evmc.EVMC_CALLCODE, + => msg.recipient, + else => @panic("unknown message kind"), + }; + + evmclog.debug("call() kind={d} depth={d} sender={} recipient={} gas={}", .{ msg.kind, msg.depth, fmtSliceHexLower(&msg.sender.bytes), fmtSliceHexLower(&msg.recipient.bytes), msg.gas }); + + if (msg.depth > params.stack_depth_limit) { + return .{ + .status_code = evmc.EVMC_CALL_DEPTH_EXCEEDED, + .gas_left = 0, + .gas_refund = 0, + .output_data = null, + .output_size = 0, + .release = null, + .create_address = std.mem.zeroes(evmc.struct_evmc_address), + .padding = [_]u8{0} ** 4, + }; + } + + vm.env.state.putAccessedAccount(recipient) catch |err| switch (err) { + error.OutOfMemory => @panic("OOO"), + }; + + if (msg.kind == evmc.EVMC_CREATE or msg.kind == evmc.EVMC_CREATE2) { + // Increment the nonce of the contract creator. + vm.env.state.incrementNonce(sender) catch unreachable; + } + + // Persist current context in case we need it for scope revert. + var prev_statedb = vm.env.state.snapshot() catch |err| switch (err) { + error.OutOfMemory => @panic("OOO"), + }; + + // Send value. + const value = std.mem.readInt(u256, &msg.value.bytes, std.builtin.Endian.big); + if (value > 0) { + const sender_balance = vm.env.state.getAccount(sender).balance; + if (sender_balance < value) { + return .{ + .status_code = evmc.EVMC_INSUFFICIENT_BALANCE, + .gas_left = 0, + .gas_refund = 0, + .output_data = null, + .output_size = 0, + .release = null, + .create_address = std.mem.zeroes(evmc.struct_evmc_address), + .padding = [_]u8{0} ** 4, + }; + } + vm.env.state.setBalance(sender, sender_balance - value) catch |err| switch (err) { + error.OutOfMemory => @panic("OOO"), + }; + const recipient_balance = vm.env.state.getAccount(recipient).balance; + vm.env.state.setBalance(recipient, recipient_balance + value) catch |err| switch (err) { + error.OutOfMemory => @panic("OOO"), + }; + } + + var result = vm.evm.*.execute.?( + vm.evm, + @ptrCast(&vm.host), + @ptrCast(vm), + evmc.EVMC_SHANGHAI, // TODO: generalize from block_number. + &msg, + code.ptr, + code.len, + ); + + if (result.status_code == evmc.EVMC_SUCCESS) { + if (msg.kind == evmc.EVMC_CREATE or msg.kind == evmc.EVMC_CREATE2) { + const contract_code_gas = @as(i64, @intCast(result.output_size)) * params.gas_code_deposit; + + if ((result.output_size > 0 and result.output_data == 0xEF) or result.output_size > params.max_code_size or contract_code_gas > result.gas_left) { + result.release.?(&result); + vm.env.state.* = prev_statedb; + return .{ + .status_code = evmc.EVMC_FAILURE, + .gas_left = 0, + .gas_refund = 0, + .output_data = null, + .output_size = 0, + .release = null, + .create_address = std.mem.zeroes(evmc.struct_evmc_address), + .padding = [_]u8{0} ** 4, + }; + } + result.gas_left -= contract_code_gas; + result.create_address = msg.recipient; + + // Save new contract code and set nonce to 1. + const contract_code = if (result.output_size == 0) &[_]u8{} else result.output_data[0..result.output_size]; + vm.env.state.setContractCode(fromEVMCAddress(msg.recipient), contract_code) catch |err| switch (err) { + error.OutOfMemory => @panic("OOO"), + error.AccountAlreadyHasCode => @panic("account already has code"), + }; + vm.env.state.incrementNonce(fromEVMCAddress(msg.recipient)) catch unreachable; + } + // Free the backup and indirectly commit to the changes that happened. + prev_statedb.deinit(); + + // EIP-158. + if (vm.env.state.isEmpty(recipient)) + vm.env.state.addTouchedAddress(recipient) catch |err| switch (err) { + error.OutOfMemory => @panic("OOO"), + }; + } else { + // If the *CALL failed, we restore the previous statedb. + vm.env.state.* = prev_statedb; + } + evmclog.debug("call() end depth={d} status_code={} gas_left={} create_address={}", .{ msg.depth, result.status_code, result.gas_left, std.fmt.fmtSliceHexLower(&result.create_address.bytes) }); + + return result; + } +}; + +// toEVMCAddress transforms an Address or ?Address into an evmc_address. +fn toEVMCAddress(address: anytype) evmc.struct_evmc_address { + const addr_typeinfo = @typeInfo(@TypeOf(address)); + if (@TypeOf(address) != Address and addr_typeinfo.optional.child != Address) { + @compileError("address must be of type Address or ?Address"); + } + + // Address type. + if (@TypeOf(address) == Address) { + return evmc.struct_evmc_address{ + .bytes = address, + }; + } + if (address) |addr| { + return toEVMCAddress(addr); + } + return evmc.struct_evmc_address{ + .bytes = [_]u8{0} ** 20, + }; +} + +fn fromEVMCAddress(address: evmc.struct_evmc_address) Address { + return address.bytes; +} + +fn toEVMCUint256Be(num: u256) evmc.evmc_uint256be { + return .{ + .bytes = blk: { + var ret: [32]u8 = undefined; + std.mem.writeInt(u256, &ret, num, std.builtin.Endian.big); + break :blk ret; + }, + }; +} + +pub const MessageCallOutput = struct { + success: bool, + gas_left: u64, + refund_counter: u64, + // logs: Union[Tuple[()], Tuple[Log, ...]] TODO + // accounts_to_delete: AddressKeySet, // TODO (delete?) +}; diff --git a/src/common/common.zig b/src/common/common.zig index 95f72f8..6cc9fe8 100644 --- a/src/common/common.zig +++ b/src/common/common.zig @@ -1,5 +1,6 @@ const std = @import("std"); -const types = @import("../types/types.zig"); +const lib = @import("lib"); +const types = lib.types; const hexutils = @import("hexutils.zig"); const rlp = @import("rlp.zig"); const contract = @import("contract.zig"); diff --git a/src/common/contract.zig b/src/common/contract.zig index 4e2aab0..3dd06f7 100644 --- a/src/common/contract.zig +++ b/src/common/contract.zig @@ -1,7 +1,8 @@ const std = @import("std"); +const lib = @import("lib"); const Allocator = std.mem.Allocator; const rlp = @import("zig-rlp"); -const types = @import("../types/types.zig"); +const types = lib.types; const Keccak256 = std.crypto.hash.sha3.Keccak256; // TODO: with careful calculation, we could avoid the allocator. diff --git a/src/engine_api/engine_api.zig b/src/engine_api/engine_api.zig index ef45d21..95eb973 100644 --- a/src/engine_api/engine_api.zig +++ b/src/engine_api/engine_api.zig @@ -1,12 +1,12 @@ const std = @import("std"); +const lib = @import("lib"); const fmt = std.fmt; -const types = @import("../types/types.zig"); -const common = @import("../common/common.zig"); +const types = lib.types; +const common = lib.common; const Allocator = std.mem.Allocator; const Withdrawal = types.Withdrawal; const Tx = types.Tx; const ExecutionPayload = execution_payload.ExecutionPayload; -const lib = @import("../lib.zig"); const Fork = lib.blockchain.Fork; pub const execution_payload = @import("execution_payload.zig"); diff --git a/src/engine_api/execution_payload.zig b/src/engine_api/execution_payload.zig index 8862fef..f4b90f1 100644 --- a/src/engine_api/execution_payload.zig +++ b/src/engine_api/execution_payload.zig @@ -1,7 +1,7 @@ const std = @import("std"); -const types = @import("../types/types.zig"); -const lib = @import("../lib.zig"); -const version = @import("../version.zig"); +const lib = @import("lib"); +const types = lib.types; +const version = lib.version; const Blockchain = lib.blockchain.Blockchain; const state = lib.state; const Allocator = std.mem.Allocator; diff --git a/src/lib.zig b/src/lib.zig index fab1236..3c4fef2 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -3,22 +3,12 @@ const std = @import("std"); pub const state = @import("state/state.zig"); pub const types = @import("types/types.zig"); pub const blockchain = @import("blockchain/blockchain.zig"); +pub const blockchain_types = @import("blockchain/types.zig"); pub const crypto = @import("crypto/crypto.zig"); pub const signer = @import("signer/signer.zig"); pub const engine_api = @import("engine_api/engine_api.zig"); pub const mpt = @import("mpt/mpt.zig"); pub const config = @import("config/config.zig"); - -test "tests" { - std.testing.log_level = .debug; - - std.testing.refAllDeclsRecursive(@import("blockchain/blockchain.zig")); - std.testing.refAllDeclsRecursive(@import("config/config.zig")); - std.testing.refAllDeclsRecursive(@import("crypto/crypto.zig")); - std.testing.refAllDeclsRecursive(@import("engine_api/engine_api.zig")); - std.testing.refAllDeclsRecursive(@import("tests/spec_tests.zig")); - std.testing.refAllDeclsRecursive(@import("tests/custom_tests.zig")); - std.testing.refAllDeclsRecursive(@import("state/state.zig")); - std.testing.refAllDeclsRecursive(@import("types/types.zig")); - std.testing.refAllDeclsRecursive(@import("mpt/mpt.zig")); -} +pub const common = @import("common/common.zig"); +pub const version = @import("version.zig"); +pub const fork = @import("blockchain/fork.zig"); diff --git a/src/main.zig b/src/main.zig index 6fde5dc..77e5c8e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,23 +1,23 @@ const std = @import("std"); -const lib = @import("lib.zig"); +const lib = @import("lib"); const ChainConfig = lib.config.ChainConfig; -const types = @import("types/types.zig"); -const crypto = @import("crypto/crypto.zig"); +const types = lib.types; +const crypto = lib.crypto; const ecdsa = crypto.ecdsa; -const AccountState = @import("state/state.zig").AccountState; +const AccountState = lib.state.AccountState; const Address = types.Address; -const VM = @import("blockchain/vm.zig").VM; -const StateDB = @import("state/state.zig").StateDB; +const VM = @import("vm").VM; +const StateDB = lib.state.StateDB; const Block = types.Block; const BlockHeader = types.BlockHeader; const Tx = types.Tx; -const TxSigner = @import("signer/signer.zig").TxSigner; +const TxSigner = lib.signer.TxSigner; const Hash32 = types.Hash32; const httpz = @import("httpz"); -const engine_api = @import("engine_api/engine_api.zig"); +const engine_api = lib.engine_api; const json = std.json; const simargs = @import("simargs"); -const version = @import("version.zig").version; +const version = lib.version.version; const Blockchain = lib.blockchain.Blockchain; const Fork = lib.blockchain.Fork; diff --git a/src/tests/custom_tests.zig b/src/tests/custom_tests.zig index e2172f9..8ece6f6 100644 --- a/src/tests/custom_tests.zig +++ b/src/tests/custom_tests.zig @@ -1,13 +1,14 @@ const std = @import("std"); -const config = @import("../config/config.zig"); -const common = @import("../common/common.zig"); -const vm = @import("../blockchain/vm.zig"); -const state = @import("../state/state.zig"); -const blockchain = @import("../blockchain/blockchain.zig"); -const blockchain_types = @import("../blockchain/types.zig"); +const lib = @import("lib"); +const config = lib.config; +const common = lib.common; +const vm = @import("vm"); +const state = lib.state; +const blockchain = lib.blockchain; +const blockchain_types = lib.blockchain_types; const Message = blockchain_types.Message; const Environment = blockchain_types.Environment; -const types = @import("../types/types.zig"); +const types = lib.types; const Hash32 = types.Hash32; const Address = types.Address; const StateDB = state.StateDB; diff --git a/src/tests/lib_tests.zig b/src/tests/lib_tests.zig new file mode 100644 index 0000000..b5d64cf --- /dev/null +++ b/src/tests/lib_tests.zig @@ -0,0 +1,11 @@ +const std = @import("std"); +const lib = @import("lib"); +const custom = @import("./custom_tests.zig"); +const spec = @import("./spec_tests.zig"); + +test "tests" { + std.testing.log_level = .debug; + std.testing.refAllDecls(lib); + std.testing.refAllDeclsRecursive(custom); + std.testing.refAllDeclsRecursive(spec); +} diff --git a/src/tests/spec_tests.zig b/src/tests/spec_tests.zig index e66e1fb..3539b56 100644 --- a/src/tests/spec_tests.zig +++ b/src/tests/spec_tests.zig @@ -1,13 +1,14 @@ const std = @import("std"); +const lib = @import("lib"); const rlp = @import("rlp"); -const config = @import("../config/config.zig"); -const types = @import("../types/types.zig"); -const blockchain = @import("../blockchain/blockchain.zig"); -const vm = @import("../blockchain/vm.zig"); -const ecdsa = @import("../crypto/crypto.zig").ecdsa; -const state = @import("../state/state.zig"); -const common = @import("../common/common.zig"); -const TxSigner = @import("../signer/signer.zig").TxSigner; +const config = lib.config; +const types = lib.types; +const blockchain = lib.blockchain; +const vm = @import("vm"); +const ecdsa = lib.crypto.ecdsa; +const state = lib.state; +const common = lib.common; +const TxSigner = lib.signer.TxSigner; const Allocator = std.mem.Allocator; const Address = types.Address; const Block = types.Block; From 297c66da637393a5763a8516336d5d64260ba13b Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 28 May 2025 22:19:00 +0200 Subject: [PATCH 08/13] fix zevem integration Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> --- build.zig | 33 +-- build.zig.zon | 4 +- src/blockchain/vm_zevem.zig | 472 ++---------------------------------- src/main.zig | 2 +- 4 files changed, 38 insertions(+), 473 deletions(-) diff --git a/build.zig b/build.zig index 83af9a4..79c4865 100644 --- a/build.zig +++ b/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const LazyPath = std.Build.LazyPath; // extract version string from build.zig.zon. The zon parser hasn't been merged @@ -38,6 +39,7 @@ fn gitRevision(b: *std.Build) []const u8 { // runner. pub fn build(b: *std.Build) !void { const version_file_path = "src/version.zig"; + const use_zevem = b.option(bool, "use-zevem", "Use zevem instead of evmone (default=false if target = host, true otherwise)") orelse false; var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); @@ -68,6 +70,7 @@ pub fn build(b: *std.Build) !void { const optimize = b.standardOptimizeOption(.{}); const dep_rlp = b.dependency("rlp", .{ .target = target, .optimize = optimize }); + const zevem = b.dependency("zevem", .{ .target = target, .optimize = optimize }); const depSecp256k1 = b.dependency("zig_eth_secp256k1", .{ .target = target, .optimize = optimize }); const mod_secp256k1 = depSecp256k1.module("zig-eth-secp256k1"); const httpz = b.dependency("httpz", .{ @@ -76,10 +79,6 @@ pub fn build(b: *std.Build) !void { }); const mod_httpz = httpz.module("httpz"); - const evmone_cmake_config_step = b.addSystemCommand(&.{ "cmake", "-S", "evmone", "-B", "zig-out/evmone_build" }); - const evmone_cmake_build_step = b.addSystemCommand(&.{ "cmake", "--build", "zig-out/evmone_build" }); - evmone_cmake_build_step.step.dependOn(&evmone_cmake_config_step.step); - const zigcli = b.dependency("zigcli", .{}); const lib_mod = b.createModule(.{ @@ -104,13 +103,21 @@ pub fn build(b: *std.Build) !void { .target = target, .optimize = optimize, }); + const unit_tests = b.addTest(.{ + .root_source_file = b.path("src/tests/lib_tests.zig"), + .target = target, + .optimize = optimize, + }); - const vm_mod = if (target.result.os.tag == .freestanding) vm_mod: { - break :vm_mod b.createModule(.{ + const is_target_host = (builtin.target.os.tag == target.result.os.tag or builtin.target.cpu.arch == target.result.cpu.arch or builtin.target.abi == target.result.abi); + const vm_mod = if (use_zevem or !is_target_host) vm_mod: { + const vm_mod = b.createModule(.{ .root_source_file = b.path("src/blockchain/vm_zevem.zig"), .target = target, .optimize = optimize, }); + vm_mod.addImport("zevem", zevem.module("zevem")); + break :vm_mod vm_mod; } else vm_mod: { const vm_mod = b.createModule(.{ .root_source_file = b.path("src/blockchain/vm_evmc.zig"), @@ -121,8 +128,14 @@ pub fn build(b: *std.Build) !void { vm_mod.addIncludePath(b.path("evmone/evmc/include")); vm_mod.addLibraryPath(b.path("zig-out/evmone_build/lib")); lib.linkSystemLibrary("evmone"); + + // use cmake to build evmone for now + const evmone_cmake_config_step = b.addSystemCommand(&.{ "cmake", "-S", "evmone", "-B", "zig-out/evmone_build" }); + const evmone_cmake_build_step = b.addSystemCommand(&.{ "cmake", "--build", "zig-out/evmone_build" }); + evmone_cmake_build_step.step.dependOn(&evmone_cmake_config_step.step); lib.step.dependOn(&evmone_cmake_build_step.step); exe.step.dependOn(&evmone_cmake_build_step.step); + unit_tests.step.dependOn(&evmone_cmake_build_step.step); break :vm_mod vm_mod; }; vm_mod.addImport("lib", lib_mod); @@ -165,13 +178,6 @@ pub fn build(b: *std.Build) !void { const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); - // Creates a step for unit testing. This only builds the test executable - // but does not run it. - const unit_tests = b.addTest(.{ - .root_source_file = b.path("src/tests/lib_tests.zig"), - .target = target, - .optimize = optimize, - }); unit_tests.root_module.addImport("lib", lib_mod); unit_tests.addLibraryPath(b.path("zig-out/evmone_build/lib")); unit_tests.linkSystemLibrary("evmone"); @@ -181,7 +187,6 @@ pub fn build(b: *std.Build) !void { unit_tests.linkLibrary(depSecp256k1.artifact("secp256k1")); unit_tests.root_module.addImport("zig-eth-secp256k1", mod_secp256k1); unit_tests.root_module.addImport("pretty-table", zigcli.module("pretty-table")); - unit_tests.step.dependOn(&evmone_cmake_build_step.step); const run_unit_tests = b.addRunArtifact(unit_tests); run_unit_tests.has_side_effects = true; diff --git a/build.zig.zon b/build.zig.zon index 576d23a..be91301 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -20,8 +20,8 @@ .hash = "zig_eth_secp256k1-0.1.0-_U97sBzsDACmwsVCfA9UA3UPr8pK8qaSeLHx97Q_Nnzu", }, .zevem = .{ - .url = "https://github.com/tsujp/zevem/archive/3fab1c1.tar.gz", - .hash = "zevem-0.0.0-ur64bt16AQDrmtK17yXjv2QTRQNxHY1ZFs9KLPrO_Vyq", + .url = "https://github.com/gballet/zevem/archive/73b9805.tar.gz", + .hash = "zevem-0.0.0-ur64biZ6AQCSZip8ILCEonv9qtqIHWeymNEbN6ETxfwK", }, }, .paths = .{""}, diff --git a/src/blockchain/vm_zevem.zig b/src/blockchain/vm_zevem.zig index e2d3805..eebe59d 100644 --- a/src/blockchain/vm_zevem.zig +++ b/src/blockchain/vm_zevem.zig @@ -1,41 +1,33 @@ -const zevem = zevem.EVM; -const EVM = zevem.EVM; +const zevem = @import("zevem"); const std = @import("std"); -const types = @import("../types/types.zig"); -const common = @import("../common/common.zig"); -const params = @import("params.zig"); -const blockchain_types = @import("types.zig"); +const lib = @import("lib"); +const common = lib.common; const Allocator = std.mem.Allocator; -const AddressSet = common.AddressSet; -const AddressKey = common.AddressKey; -const AddressKeySet = common.AddressKeySet; -const Environment = blockchain_types.Environment; -const Message = blockchain_types.Message; -const Block = types.Block; -const Hash32 = types.Hash32; -const Address = types.Address; -const Keccak256 = std.crypto.hash.sha3.Keccak256; -const fmtSliceHexLower = std.fmt.fmtSliceHexLower; -const assert = std.debug.assert; +const Environment = lib.blockchain_types.Environment; +const Message = lib.blockchain_types.Message; const empty_hash = common.comptimeHexToBytes("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); const EnvFuncs = struct {}; +const EVM = zevem.evm.New(EnvFuncs); pub const VM = struct { const vmlog = std.log.scoped(.vm); allocator: Allocator, env: Environment, + envfuncs: *EnvFuncs, evm: EVM, // init creates a new EVM VM instance. The caller must call deinit() when done. pub fn init(allocator: Allocator, env: Environment) VM { - const evm = EVM.initi(EnvFuncs{}); + const envfuncs = allocator.create(EnvFuncs) catch @panic("error allocating environment function"); + const evm = EVM.init(allocator, envfuncs) catch @panic("error in evm init"); return .{ .allocator = allocator, .env = env, .evm = evm, + .envfuncs = envfuncs, }; } @@ -44,456 +36,24 @@ pub const VM = struct { if (self.evm.*.destroy) |destroy| { destroy(self.evm); } + self.allocator.destroy(self.envfuncs); } // processMessageCall executes a message call. pub fn processMessageCall(self: *VM, msg: Message) !MessageCallOutput { - if (msg.target) { + if (msg.target != null) { try self.env.state.incrementNonce(msg.sender); } - const result = self.evm.execute(); + try self.evm.execute(); return .{ - .gas_left = @intCast(result.gas_left), - .refund_counter = @intCast(result.gas_refund), - .success = result.status_code == evmc.EVMC_SUCCESS, + .gas_left = 0, + .refund_counter = 0, + .success = true, }; } }; -// EVMOneHost contains the implementation of the EVMC host interface. -// https://evmc.ethereum.org/structevmc__host__interface.html -const EVMOneHost = struct { - const evmclog = std.log.scoped(.evmone); - - fn get_tx_context(ctx: ?*evmc.struct_evmc_host_context) callconv(.C) evmc.struct_evmc_tx_context { - evmclog.debug("getTxContext", .{}); - - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - return evmc.struct_evmc_tx_context{ - .tx_gas_price = toEVMCUint256Be(vm.env.gas_price), - .tx_origin = toEVMCAddress(vm.env.origin), - .block_coinbase = toEVMCAddress(vm.env.coinbase), - .block_number = @intCast(vm.env.number), - .block_timestamp = @intCast(vm.env.time), - .block_gas_limit = @intCast(vm.env.gas_limit), - .block_prev_randao = .{ .bytes = vm.env.prev_randao }, - .chain_id = toEVMCUint256Be(@intFromEnum(vm.env.chain_id)), - .block_base_fee = toEVMCUint256Be(vm.env.base_fee_per_gas), - }; - } - - fn get_block_hash(ctx: ?*evmc.struct_evmc_host_context, block_number: i64) callconv(.C) evmc.evmc_bytes32 { - evmclog.debug("getBlockHash block_number={}", .{block_number}); - - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - const idx = vm.env.number - @as(u64, @intCast(block_number)); - return .{ .bytes = vm.env.fork.get_parent_block_hash(idx) catch @panic("unhandled error getting parent hash") }; - } - - fn account_exists(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) bool { - const address = fromEVMCAddress(addr.*); - evmclog.debug("accountExists addr=0x{}", .{fmtSliceHexLower(&address)}); - - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - - return vm.env.state.getAccountOpt(address) != null; - } - - fn get_storage( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - key: [*c]const evmc.evmc_bytes32, - ) callconv(.C) evmc.evmc_bytes32 { - const address = fromEVMCAddress(addr.*); - evmclog.debug("getStorage addr=0x{} key={}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&key.*.bytes) }); - - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - const k = std.mem.readInt(u256, &key.*.bytes, std.builtin.Endian.big); - - return .{ .bytes = vm.env.state.getStorage(address, k) }; - } - - fn set_storage( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - key: [*c]const evmc.evmc_bytes32, - value: [*c]const evmc.evmc_bytes32, - ) callconv(.C) evmc.enum_evmc_storage_status { - const address = fromEVMCAddress(addr.*); - evmclog.debug("setStorage addr=0x{} key={} value={}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&key.*.bytes), fmtSliceHexLower(&value.*.bytes) }); - - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - - const k = std.mem.readInt(u256, &key.*.bytes, std.builtin.Endian.big); - const storage_status: evmc.enum_evmc_storage_status = blk: { - const original_value = vm.env.state.getOriginalStorage(address, k); - const current_value = vm.env.state.getStorage(address, k); - const new_value = value.*.bytes; - const zero = std.mem.zeroes([32]u8); - - // See: https://evmc.ethereum.org/group__EVMC.html#gae012fd6b8e5c23806b507c2d3e9fb1aa - - // EIP-220: 2. - if (std.mem.eql(u8, ¤t_value, &new_value)) { - break :blk evmc.EVMC_STORAGE_ASSIGNED; - } - // EIP-220: 3. - - // EIP-220: 3.1 - if (std.mem.eql(u8, &original_value, ¤t_value)) { - // EIP-220: 3.1.1 - if (std.mem.eql(u8, &original_value, &zero)) { - // 0->0->Z - break :blk evmc.EVMC_STORAGE_ADDED; - } - if (std.mem.eql(u8, &new_value, &zero)) { - // X->X->0 - break :blk evmc.EVMC_STORAGE_DELETED; - } - // X->X->Z - break :blk evmc.EVMC_STORAGE_MODIFIED; - } - - // EIP-220: 3.2 - // X != Y - - // EIP-220: 3.2.1 - if (!std.mem.eql(u8, &original_value, &zero)) { - // EIP-220: 3.2.1.1 - if (std.mem.eql(u8, ¤t_value, &zero)) { - // X->0->Z - break :blk evmc.EVMC_STORAGE_DELETED_ADDED; - } - // EIP-220: 3.2.1.2 - if (std.mem.eql(u8, &new_value, &zero)) { - // X->Y->0 - break :blk evmc.EVMC_STORAGE_MODIFIED_DELETED; - } - } - - // EIP-220: 3.2.2 - if (std.mem.eql(u8, &original_value, &new_value)) { - if (std.mem.eql(u8, ¤t_value, &zero)) { - // X->0->X - break :blk evmc.EVMC_STORAGE_DELETED_RESTORED; - } - // EIP-220: 3.2.2.1 - if (std.mem.eql(u8, &original_value, &zero)) { - // 0->Y->0 - break :blk evmc.EVMC_STORAGE_ADDED_DELETED; - } - // X->Y->X - break :blk evmc.EVMC_STORAGE_MODIFIED_RESTORED; - } - - break :blk evmc.EVMC_STORAGE_ASSIGNED; - }; - - vm.env.state.setStorage(address, k, value.*.bytes) catch |err| switch (err) { - // From EVMC docs: "The VM MUST make sure that the account exists. This requirement is only a formality - // because VM implementations only modify storage of the account of the current execution context". - error.AccountDoesNotExist => @panic("set storage in non-existent account"), - error.OutOfMemory => @panic("OOO"), - }; - - return storage_status; - } - - fn get_balance(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) evmc.evmc_uint256be { - const address = fromEVMCAddress(addr.*); - evmclog.debug("getBalance addr=0x{})", .{fmtSliceHexLower(&address)}); - - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - - return toEVMCUint256Be(vm.env.state.getAccount(address).balance); - } - - fn get_code_size(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) usize { - const address = fromEVMCAddress(addr.*); - evmclog.debug("getCodeSize addr=0x{})", .{fmtSliceHexLower(&address)}); - - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - - return vm.env.state.getAccount(address).code.len; - } - - fn get_code_hash( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - ) callconv(.C) evmc.evmc_bytes32 { - const address = fromEVMCAddress(addr.*); - evmclog.debug("getCodeHash addr=0x{})", .{fmtSliceHexLower(&address)}); - - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - var ret = empty_hash; - const code = vm.env.state.getAccount(address).code; - if (code.len > 0) - Keccak256.hash(code, &ret, .{}); - - return .{ .bytes = ret }; - } - - fn copy_code( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - code_offset: usize, - buffer_data: [*c]u8, - buffer_size: usize, - ) callconv(.C) usize { - const address = fromEVMCAddress(addr.*); - evmclog.debug("copyCode addr=0x{} code_offset={})", .{ fmtSliceHexLower(&address), code_offset }); - - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - const code = vm.env.state.getAccount(address).code; - - const copy_len = @min(buffer_size, code.len - code_offset); - @memcpy(buffer_data[0..copy_len], code[code_offset..][0..copy_len]); - - return copy_len; - } - - fn self_destruct( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - addr2: [*c]const evmc.evmc_address, - ) callconv(.C) bool { - _ = addr2; - _ = addr; - _ = ctx; - // https://evmc.ethereum.org/group__EVMC.html#ga1aa9fa657b3f0de375e2f07e53b65bcc - @panic("TODO"); - } - - fn emit_log( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - data: [*c]const u8, - data_size: usize, - topics: [*c]const evmc.evmc_bytes32, - topics_count: usize, - ) callconv(.C) void { - _ = topics_count; - _ = topics; - _ = data_size; - _ = data; - _ = addr; - _ = ctx; - // https://evmc.ethereum.org/group__EVMC.html#gaab96621b67d653758b3da15c2b596938 - @panic("TODO"); - } - - fn access_account(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) evmc.enum_evmc_access_status { - const address = fromEVMCAddress(addr.*); - evmclog.debug("accessAccount addr=0x{}", .{fmtSliceHexLower(&address)}); - - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - if (vm.env.state.accessedAccountsContains(address)) - return evmc.EVMC_ACCESS_WARM; - vm.env.state.putAccessedAccount(address) catch |err| switch (err) { - error.OutOfMemory => @panic("OOO"), - }; - - return evmc.EVMC_ACCESS_COLD; - } - - fn access_storage( - ctx: ?*evmc.struct_evmc_host_context, - addr: [*c]const evmc.evmc_address, - key: [*c]const evmc.evmc_bytes32, - ) callconv(.C) evmc.enum_evmc_access_status { - const address = fromEVMCAddress(addr.*); - evmclog.debug("accessStorage addr=0x{} key=0x{}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&key.*.bytes) }); - - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - const address_key: AddressKey = .{ .address = address, .key = key.*.bytes }; - if (vm.env.state.accessedStorageKeysContains(address_key)) - return evmc.EVMC_ACCESS_WARM; - _ = vm.env.state.putAccessedStorageKeys(address_key) catch |err| switch (err) { - error.OutOfMemory => @panic("OOO"), - }; - - return evmc.EVMC_ACCESS_COLD; - } - - fn call(ctx: ?*evmc.struct_evmc_host_context, _msg: [*c]const evmc.struct_evmc_message) callconv(.C) evmc.struct_evmc_result { - const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); - - var msg = _msg.*; - - const code = switch (msg.kind) { - evmc.EVMC_CALL, - evmc.EVMC_DELEGATECALL, - evmc.EVMC_CALLCODE, - => vm.env.state.getAccount(fromEVMCAddress(msg.code_address)).code, - evmc.EVMC_CREATE, - evmc.EVMC_CREATE2, - => if (msg.input_size == 0) &[_]u8{} else msg.input_data[0..msg.input_size], - else => @panic("unknown message kind"), - }; - - const sender = fromEVMCAddress(msg.sender); - const recipient = fromEVMCAddress(msg.recipient); - - msg.recipient = switch (msg.kind) { - evmc.EVMC_CREATE => blk: { - const sender_nonce: u64 = @intCast(vm.env.state.getAccount(sender).nonce); - break :blk .{ .bytes = common.computeCREATEContractAddress(vm.allocator, sender, sender_nonce) catch unreachable }; - }, - evmc.EVMC_CREATE2 => .{ .bytes = common.computeCREATE2ContractAddress(sender, msg.create2_salt.bytes, code) catch unreachable }, - evmc.EVMC_CALL, - evmc.EVMC_DELEGATECALL, - evmc.EVMC_CALLCODE, - => msg.recipient, - else => @panic("unknown message kind"), - }; - - evmclog.debug("call() kind={d} depth={d} sender={} recipient={} gas={}", .{ msg.kind, msg.depth, fmtSliceHexLower(&msg.sender.bytes), fmtSliceHexLower(&msg.recipient.bytes), msg.gas }); - - if (msg.depth > params.stack_depth_limit) { - return .{ - .status_code = evmc.EVMC_CALL_DEPTH_EXCEEDED, - .gas_left = 0, - .gas_refund = 0, - .output_data = null, - .output_size = 0, - .release = null, - .create_address = std.mem.zeroes(evmc.struct_evmc_address), - .padding = [_]u8{0} ** 4, - }; - } - - vm.env.state.putAccessedAccount(recipient) catch |err| switch (err) { - error.OutOfMemory => @panic("OOO"), - }; - - if (msg.kind == evmc.EVMC_CREATE or msg.kind == evmc.EVMC_CREATE2) { - // Increment the nonce of the contract creator. - vm.env.state.incrementNonce(sender) catch unreachable; - } - - // Persist current context in case we need it for scope revert. - var prev_statedb = vm.env.state.snapshot() catch |err| switch (err) { - error.OutOfMemory => @panic("OOO"), - }; - - // Send value. - const value = std.mem.readInt(u256, &msg.value.bytes, std.builtin.Endian.big); - if (value > 0) { - const sender_balance = vm.env.state.getAccount(sender).balance; - if (sender_balance < value) { - return .{ - .status_code = evmc.EVMC_INSUFFICIENT_BALANCE, - .gas_left = 0, - .gas_refund = 0, - .output_data = null, - .output_size = 0, - .release = null, - .create_address = std.mem.zeroes(evmc.struct_evmc_address), - .padding = [_]u8{0} ** 4, - }; - } - vm.env.state.setBalance(sender, sender_balance - value) catch |err| switch (err) { - error.OutOfMemory => @panic("OOO"), - }; - const recipient_balance = vm.env.state.getAccount(recipient).balance; - vm.env.state.setBalance(recipient, recipient_balance + value) catch |err| switch (err) { - error.OutOfMemory => @panic("OOO"), - }; - } - - var result = vm.evm.*.execute.?( - vm.evm, - @ptrCast(&vm.host), - @ptrCast(vm), - evmc.EVMC_SHANGHAI, // TODO: generalize from block_number. - &msg, - code.ptr, - code.len, - ); - - if (result.status_code == evmc.EVMC_SUCCESS) { - if (msg.kind == evmc.EVMC_CREATE or msg.kind == evmc.EVMC_CREATE2) { - const contract_code_gas = @as(i64, @intCast(result.output_size)) * params.gas_code_deposit; - - if ((result.output_size > 0 and result.output_data == 0xEF) or result.output_size > params.max_code_size or contract_code_gas > result.gas_left) { - result.release.?(&result); - vm.env.state.* = prev_statedb; - return .{ - .status_code = evmc.EVMC_FAILURE, - .gas_left = 0, - .gas_refund = 0, - .output_data = null, - .output_size = 0, - .release = null, - .create_address = std.mem.zeroes(evmc.struct_evmc_address), - .padding = [_]u8{0} ** 4, - }; - } - result.gas_left -= contract_code_gas; - result.create_address = msg.recipient; - - // Save new contract code and set nonce to 1. - const contract_code = if (result.output_size == 0) &[_]u8{} else result.output_data[0..result.output_size]; - vm.env.state.setContractCode(fromEVMCAddress(msg.recipient), contract_code) catch |err| switch (err) { - error.OutOfMemory => @panic("OOO"), - error.AccountAlreadyHasCode => @panic("account already has code"), - }; - vm.env.state.incrementNonce(fromEVMCAddress(msg.recipient)) catch unreachable; - } - // Free the backup and indirectly commit to the changes that happened. - prev_statedb.deinit(); - - // EIP-158. - if (vm.env.state.isEmpty(recipient)) - vm.env.state.addTouchedAddress(recipient) catch |err| switch (err) { - error.OutOfMemory => @panic("OOO"), - }; - } else { - // If the *CALL failed, we restore the previous statedb. - vm.env.state.* = prev_statedb; - } - evmclog.debug("call() end depth={d} status_code={} gas_left={} create_address={}", .{ msg.depth, result.status_code, result.gas_left, std.fmt.fmtSliceHexLower(&result.create_address.bytes) }); - - return result; - } -}; - -// toEVMCAddress transforms an Address or ?Address into an evmc_address. -fn toEVMCAddress(address: anytype) evmc.struct_evmc_address { - const addr_typeinfo = @typeInfo(@TypeOf(address)); - if (@TypeOf(address) != Address and addr_typeinfo.optional.child != Address) { - @compileError("address must be of type Address or ?Address"); - } - - // Address type. - if (@TypeOf(address) == Address) { - return evmc.struct_evmc_address{ - .bytes = address, - }; - } - if (address) |addr| { - return toEVMCAddress(addr); - } - return evmc.struct_evmc_address{ - .bytes = [_]u8{0} ** 20, - }; -} - -fn fromEVMCAddress(address: evmc.struct_evmc_address) Address { - return address.bytes; -} - -fn toEVMCUint256Be(num: u256) evmc.evmc_uint256be { - return .{ - .bytes = blk: { - var ret: [32]u8 = undefined; - std.mem.writeInt(u256, &ret, num, std.builtin.Endian.big); - break :blk ret; - }, - }; -} - pub const MessageCallOutput = struct { success: bool, gas_left: u64, diff --git a/src/main.zig b/src/main.zig index 77e5c8e..106dc6c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -97,7 +97,7 @@ pub fn main() !void { // TODO print usage upon failure (requires upstream changes) // TODO generate version from build and add it here - const opts = try simargs.parse(gpa.allocator(), PhantArgs, "", version); + const opts = try simargs.parse(allocator, PhantArgs, "", version); defer opts.deinit(); const port: u16 = if (opts.args.engine_api_port == null) 8551 else opts.args.engine_api_port.?; From 2c9a07ea5fe1008209e1d2c104349b35066dcad9 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 7 Aug 2025 21:42:02 +0200 Subject: [PATCH 09/13] various fixes that I hadn't committed until now Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> --- src/blockchain/vm_zevem.zig | 6 ++++-- src/tests/lib_tests.zig | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/blockchain/vm_zevem.zig b/src/blockchain/vm_zevem.zig index eebe59d..5954e17 100644 --- a/src/blockchain/vm_zevem.zig +++ b/src/blockchain/vm_zevem.zig @@ -8,7 +8,9 @@ const Message = lib.blockchain_types.Message; const empty_hash = common.comptimeHexToBytes("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); -const EnvFuncs = struct {}; +const EnvFuncs = struct { + pub fn getBalance(_: *EnvFuncs) void {} +}; const EVM = zevem.evm.New(EnvFuncs); pub const VM = struct { @@ -45,7 +47,7 @@ pub const VM = struct { try self.env.state.incrementNonce(msg.sender); } - try self.evm.execute(); + try self.evm.execute(self.env.state.getAccount(msg.target.?).code); return .{ .gas_left = 0, .refund_counter = 0, diff --git a/src/tests/lib_tests.zig b/src/tests/lib_tests.zig index b5d64cf..e07231b 100644 --- a/src/tests/lib_tests.zig +++ b/src/tests/lib_tests.zig @@ -5,7 +5,8 @@ const spec = @import("./spec_tests.zig"); test "tests" { std.testing.log_level = .debug; - std.testing.refAllDecls(lib); + // XXX this is commented out because of an apparent bug in zig 0.14.1 + // std.testing.refAllDeclsRecursive(lib.state); std.testing.refAllDeclsRecursive(custom); std.testing.refAllDeclsRecursive(spec); } From 7400b02b687940349e222d2791a9e640f2e6c464 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:00:51 +0100 Subject: [PATCH 10/13] upgrade zig-curl --- .github/workflows/ci.yml | 8 ++++---- build.zig.zon | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cef231d..7de5dd2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Zig uses: korandoru/setup-zig@v1 with: - zig-version: 0.14.0 + zig-version: 0.14.1 - name: Build run: git submodule update --init --recursive && zig build @@ -29,7 +29,7 @@ jobs: - name: Set up Zig uses: korandoru/setup-zig@v1 with: - zig-version: 0.14.0 + zig-version: 0.14.1 - name: Lint run: zig fmt --check src/*.zig @@ -42,7 +42,7 @@ jobs: - name: Set up Zig uses: korandoru/setup-zig@v1 with: - zig-version: 0.14.0 + zig-version: 0.14.1 - name: Test run: git submodule update --init --recursive && zig build test @@ -55,7 +55,7 @@ jobs: - name: Set up Zig uses: korandoru/setup-zig@v1 with: - zig-version: 0.14.0 + zig-version: 0.14.1 - name: Test run: git submodule update --init --recursive && zig build -Dtarget="aarch64-linux" diff --git a/build.zig.zon b/build.zig.zon index be91301..382277d 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -23,6 +23,10 @@ .url = "https://github.com/gballet/zevem/archive/73b9805.tar.gz", .hash = "zevem-0.0.0-ur64biZ6AQCSZip8ILCEonv9qtqIHWeymNEbN6ETxfwK", }, + .curl = .{ + .url = "https://github.com/jiacai2050/zig-curl/archive/refs/tags/v0.2.0.tar.gz", + .hash = "curl-0.2.0-P4tT4UfOAADebEpwArAmrbS8Bzdxnh6PEmtHhbTLdxaG", + }, }, .paths = .{""}, } From e5bb2d8d980d80f673ad569bec045a47f2ca0890 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:03:07 +0100 Subject: [PATCH 11/13] upgrade to zig-curl 0.3.0 --- build.zig.zon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 382277d..49a499b 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -24,8 +24,8 @@ .hash = "zevem-0.0.0-ur64biZ6AQCSZip8ILCEonv9qtqIHWeymNEbN6ETxfwK", }, .curl = .{ - .url = "https://github.com/jiacai2050/zig-curl/archive/refs/tags/v0.2.0.tar.gz", - .hash = "curl-0.2.0-P4tT4UfOAADebEpwArAmrbS8Bzdxnh6PEmtHhbTLdxaG", + .url = "https://github.com/jiacai2050/zig-curl/archive/refs/tags/v0.3.0.tar.gz", + .hash = "curl-0.3.0-P4tT4ZzSAADueeRzPHnzLFj9lRTJyA3rnOYycH0yu-N4", }, }, .paths = .{""}, From ee70afff19dc34fd54023b85fca10c378efe4a40 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:06:54 +0100 Subject: [PATCH 12/13] change zig installer in github actions + install cmake + use 0.14.1 --- .github/workflows/ci.yml | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7de5dd2..f5d9394 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,53 +9,57 @@ on: jobs: build: - runs-on: self-hosted + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Zig - uses: korandoru/setup-zig@v1 + uses: mlugg/setup-zig@v2 with: - zig-version: 0.14.1 + version: 0.14.1 - name: Build run: git submodule update --init --recursive && zig build lint: - runs-on: self-hosted + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Zig - uses: korandoru/setup-zig@v1 + uses: mlugg/setup-zig@v2 with: - zig-version: 0.14.1 + version: 0.14.1 - name: Lint run: zig fmt --check src/*.zig test: - runs-on: self-hosted + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Zig - uses: korandoru/setup-zig@v1 + uses: mlugg/setup-zig@v2 with: - zig-version: 0.14.1 + version: 0.14.1 + + - name: Install cmake + run: | + sudo apt update + sudo apt install -y cmake - name: Test run: git submodule update --init --recursive && zig build test build-aarch64: - runs-on: self-hosted + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - - name: Set up Zig - uses: korandoru/setup-zig@v1 - with: - zig-version: 0.14.1 + - uses: actions/checkout@v2 + - name: Set up Zig + uses: mlugg/setup-zig@v2 + with: + version: 0.14.1 - - name: Test - run: git submodule update --init --recursive && zig build -Dtarget="aarch64-linux" + - name: Build + run: git submodule update --init --recursive && zig build -Dtarget="aarch64-linux" From 807418cacb7699c4f4e9bac56f3fee224ca328fe Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:15:04 -0500 Subject: [PATCH 13/13] cross-compilation using cmake build --- .github/workflows/ci.yml | 9 ++++++-- build.zig | 44 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5d9394..2b61feb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,11 @@ jobs: uses: mlugg/setup-zig@v2 with: version: 0.14.1 - + + - name: Install cmake + run: | + sudo apt update + sudo apt install -y cmake + - name: Build - run: git submodule update --init --recursive && zig build -Dtarget="aarch64-linux" + run: git submodule update --init --recursive && zig build -Dtarget="aarch64-linux" -Dforce-evmone=true diff --git a/build.zig b/build.zig index 79c4865..692bdbe 100644 --- a/build.zig +++ b/build.zig @@ -40,6 +40,7 @@ fn gitRevision(b: *std.Build) []const u8 { pub fn build(b: *std.Build) !void { const version_file_path = "src/version.zig"; const use_zevem = b.option(bool, "use-zevem", "Use zevem instead of evmone (default=false if target = host, true otherwise)") orelse false; + const force_evmone = b.option(bool, "force-evmone", "Force evmone even when cross-compiling (uses zig cc as cmake compiler)") orelse false; var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); @@ -109,8 +110,8 @@ pub fn build(b: *std.Build) !void { .optimize = optimize, }); - const is_target_host = (builtin.target.os.tag == target.result.os.tag or builtin.target.cpu.arch == target.result.cpu.arch or builtin.target.abi == target.result.abi); - const vm_mod = if (use_zevem or !is_target_host) vm_mod: { + const is_target_host = (builtin.target.os.tag == target.result.os.tag and builtin.target.cpu.arch == target.result.cpu.arch and builtin.target.abi == target.result.abi); + const vm_mod = if (use_zevem or (!is_target_host and !force_evmone)) vm_mod: { const vm_mod = b.createModule(.{ .root_source_file = b.path("src/blockchain/vm_zevem.zig"), .target = target, @@ -130,7 +131,44 @@ pub fn build(b: *std.Build) !void { lib.linkSystemLibrary("evmone"); // use cmake to build evmone for now - const evmone_cmake_config_step = b.addSystemCommand(&.{ "cmake", "-S", "evmone", "-B", "zig-out/evmone_build" }); + const evmone_cmake_config_step = b.addSystemCommand(&.{"cmake"}); + evmone_cmake_config_step.addArgs(&.{ "-S", "evmone", "-B", "zig-out/evmone_build" }); + + if (!is_target_host) { + // Cross-compilation: use zig cc as the C/C++ compiler + const cmake_system_name = switch (target.result.os.tag) { + .linux => "Linux", + .macos => "Darwin", + .windows => "Windows", + else => @tagName(target.result.os.tag), + }; + const cmake_system_processor = switch (target.result.cpu.arch) { + .x86_64 => "x86_64", + .aarch64 => "aarch64", + .arm => "arm", + .riscv64 => "riscv64", + else => @tagName(target.result.cpu.arch), + }; + const zig_target = b.fmt("{s}-{s}-{s}", .{ + @tagName(target.result.cpu.arch), + @tagName(target.result.os.tag), + @tagName(target.result.abi), + }); + + evmone_cmake_config_step.addArgs(&.{ + b.fmt("-DCMAKE_SYSTEM_NAME={s}", .{cmake_system_name}), + b.fmt("-DCMAKE_SYSTEM_PROCESSOR={s}", .{cmake_system_processor}), + b.fmt("-DCMAKE_C_COMPILER_TARGET={s}", .{zig_target}), + b.fmt("-DCMAKE_CXX_COMPILER_TARGET={s}", .{zig_target}), + "-DCMAKE_C_COMPILER=zig", + "-DCMAKE_CXX_COMPILER=zig", + "-DCMAKE_C_COMPILER_ARG1=cc", + "-DCMAKE_CXX_COMPILER_ARG1=c++", + b.fmt("-DCMAKE_C_FLAGS=-target {s}", .{zig_target}), + b.fmt("-DCMAKE_CXX_FLAGS=-target {s}", .{zig_target}), + }); + } + const evmone_cmake_build_step = b.addSystemCommand(&.{ "cmake", "--build", "zig-out/evmone_build" }); evmone_cmake_build_step.step.dependOn(&evmone_cmake_config_step.step); lib.step.dependOn(&evmone_cmake_build_step.step);