From ccdc0bbd2a2901058ec0953e9660161ee8a5e527 Mon Sep 17 00:00:00 2001 From: Wodann Date: Wed, 20 Mar 2024 08:31:53 +0100 Subject: [PATCH] build: bump revm to 7.1 (#4968) --- .github/workflows/edr-benchmark.yml | 2 +- Cargo.lock | 254 ++++--------- crates/edr_eth/Cargo.toml | 7 +- crates/edr_eth/src/lib.rs | 6 +- crates/edr_eth/src/log.rs | 2 +- crates/edr_eth/src/log/block.rs | 49 ++- crates/edr_eth/src/log/filter.rs | 21 +- crates/edr_eth/src/log/receipt.rs | 23 +- crates/edr_eth/src/receipt.rs | 19 +- crates/edr_eth/src/remote/filter.rs | 4 +- crates/edr_evm/Cargo.toml | 5 +- crates/edr_evm/src/block.rs | 5 +- crates/edr_evm/src/block/builder.rs | 188 +++++++--- crates/edr_evm/src/blockchain.rs | 2 + .../edr_evm/src/blockchain/storage/sparse.rs | 2 +- crates/edr_evm/src/db.rs | 12 - crates/edr_evm/src/debug.rs | 37 ++ crates/edr_evm/src/debug_trace.rs | 338 +++++++++--------- crates/edr_evm/src/evm.rs | 67 ---- crates/edr_evm/src/inspector.rs | 226 ------------ crates/edr_evm/src/lib.rs | 38 +- crates/edr_evm/src/miner.rs | 46 +-- crates/edr_evm/src/runtime.rs | 164 ++++++--- crates/edr_evm/src/state/debug.rs | 2 +- crates/edr_evm/src/state/overrides.rs | 26 +- crates/edr_evm/src/trace.rs | 326 +++++++++++++---- crates/edr_evm/src/transaction.rs | 4 + crates/edr_evm/src/transaction/executable.rs | 32 +- crates/edr_evm/tests/blockchain.rs | 14 +- crates/edr_napi/index.d.ts | 2 +- crates/edr_napi/src/call_override.rs | 2 +- crates/edr_napi/src/log.rs | 4 +- crates/edr_napi/src/logger.rs | 6 +- crates/edr_napi/src/result.rs | 66 ++-- crates/edr_napi/src/trace.rs | 2 +- .../src/{data/inspector.rs => console_log.rs} | 114 ++---- crates/edr_provider/src/data.rs | 157 ++++---- crates/edr_provider/src/data/call.rs | 35 +- crates/edr_provider/src/data/gas.rs | 49 +-- crates/edr_provider/src/debug_mine.rs | 7 +- crates/edr_provider/src/debugger.rs | 63 ++++ crates/edr_provider/src/error.rs | 12 +- crates/edr_provider/src/filter/criteria.rs | 2 +- crates/edr_provider/src/lib.rs | 11 +- crates/edr_provider/src/mock.rs | 78 ++++ crates/edr_provider/src/test_utils.rs | 14 +- crates/edr_solidity/Cargo.toml | 2 +- .../edr_solidity/src/contracts_identifier.rs | 2 +- crates/edr_solidity/src/opcodes.rs | 2 +- 49 files changed, 1323 insertions(+), 1228 deletions(-) delete mode 100644 crates/edr_evm/src/db.rs create mode 100644 crates/edr_evm/src/debug.rs delete mode 100644 crates/edr_evm/src/evm.rs delete mode 100644 crates/edr_evm/src/inspector.rs rename crates/edr_provider/src/{data/inspector.rs => console_log.rs} (65%) create mode 100644 crates/edr_provider/src/debugger.rs create mode 100644 crates/edr_provider/src/mock.rs diff --git a/.github/workflows/edr-benchmark.yml b/.github/workflows/edr-benchmark.yml index 2f36d43eeb..aaf6a92b2a 100644 --- a/.github/workflows/edr-benchmark.yml +++ b/.github/workflows/edr-benchmark.yml @@ -59,7 +59,7 @@ jobs: - name: Run benchmark run: pnpm run -s benchmark - + # - name: Validate regressions # run: pnpm run -s verify diff --git a/Cargo.lock b/Cargo.lock index ec50b43c1d..6d3252f4b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,16 +45,15 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "alloy-primitives" -version = "0.4.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0628ec0ba5b98b3370bb6be17b12f23bfce8ee4ad83823325a20546d9b03b78" +checksum = "6c0e5e60ff0e0c34c553822dabcfe0f5fece5a8c52f08a915be8c737de4b03fa" dependencies = [ "alloy-rlp", "bytes", "cfg-if", "const-hex", "derive_more", - "getrandom", "hex-literal", "itoa", "proptest", @@ -66,17 +65,20 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.5.3" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c0e5e60ff0e0c34c553822dabcfe0f5fece5a8c52f08a915be8c737de4b03fa" +checksum = "ef197eb250c64962003cb08b90b17f0882c192f4a6f2f544809d424fd7cb0e7d" dependencies = [ "alloy-rlp", "bytes", "cfg-if", "const-hex", "derive_more", + "getrandom", "hex-literal", "itoa", + "k256", + "keccak-asm", "proptest", "rand", "ruint", @@ -354,16 +356,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "aurora-engine-modexp" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfacad86e9e138fca0670949eb8ed4ffdf73a55bded8887efe0863cd1a3a6f70" +dependencies = [ + "hex", + "num", +] + [[package]] name = "auto_impl" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] @@ -405,29 +416,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "bindgen" -version = "0.66.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" -dependencies = [ - "bitflags 2.4.1", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.38", - "which", -] - [[package]] name = "bit-set" version = "0.5.3" @@ -451,9 +439,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" dependencies = [ "serde", ] @@ -521,11 +509,10 @@ dependencies = [ [[package]] name = "c-kzg" -version = "0.1.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac926d808fb72fe09ebf471a091d6d72918876ccf0b4989766093d2d0d24a0ef" +checksum = "94a4bc5367b6284358d2a6a6a1dc2d92ec4b86034561c3b9d3341909752fd848" dependencies = [ - "bindgen", "blst", "cc", "glob", @@ -559,15 +546,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -623,17 +601,6 @@ dependencies = [ "rlp", ] -[[package]] -name = "clang-sys" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" -dependencies = [ - "glob", - "libc", - "libloading 0.7.4", -] - [[package]] name = "clap" version = "3.2.25" @@ -1036,7 +1003,7 @@ version = "0.2.0-dev" name = "edr_eth" version = "0.2.0-dev" dependencies = [ - "alloy-primitives 0.4.2", + "alloy-primitives 0.6.3", "alloy-rlp", "anyhow", "assert-json-diff", @@ -1171,7 +1138,7 @@ name = "edr_solidity" version = "0.2.0-dev" dependencies = [ "edr_eth", - "revm", + "edr_evm", ] [[package]] @@ -1671,15 +1638,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "home" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" -dependencies = [ - "windows-sys 0.48.0", -] - [[package]] name = "http" version = "0.2.9" @@ -1924,15 +1882,16 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", "once_cell", "sha2", + "signature", ] [[package]] @@ -1944,6 +1903,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-asm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb8515fff80ed850aea4a1595f2e519c003e2a00a82fe168ebf5269196caf444" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1953,28 +1922,12 @@ dependencies = [ "spin", ] -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - [[package]] name = "libloading" version = "0.8.1" @@ -2059,12 +2012,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.7.1" @@ -2110,7 +2057,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fc1cb00cde484640da9f01a124edbb013576a6ae9843b23857c940936b76d91" dependencies = [ "anyhow", - "bitflags 2.4.1", + "bitflags 2.4.2", "ctor", "napi-derive", "napi-sys", @@ -2161,7 +2108,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2503fa6af34dc83fb74888df8b22afe933b58d37daf7d80424b1c60c68196b8b" dependencies = [ - "libloading 0.8.1", + "libloading", ] [[package]] @@ -2182,16 +2129,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2368,7 +2305,7 @@ version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cfg-if", "foreign-types", "libc", @@ -2508,12 +2445,6 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2608,16 +2539,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "prettyplease" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" -dependencies = [ - "proc-macro2", - "syn 2.0.38", -] - [[package]] name = "primitive-types" version = "0.12.2" @@ -2683,7 +2604,7 @@ checksum = "7c003ac8c77cb07bb74f5f198bce836a689bcd5a42574612bf14d17bfd08c20e" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.4.1", + "bitflags 2.4.2", "lazy_static", "num-traits", "rand", @@ -2949,20 +2870,23 @@ dependencies = [ [[package]] name = "revm" -version = "3.5.0" -source = "git+https://github.com/Wodann/revm?rev=b25b44a#b25b44aac8132bd7086faf3bc3e5f79cbf0ded49" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217d21144d329f21d5245b8e6a46e0d6d0a527d9917d7a087f225b161e529169" dependencies = [ "auto_impl", + "cfg-if", + "dyn-clone", "revm-interpreter", "revm-precompile", "serde", - "serde_json", ] [[package]] name = "revm-interpreter" -version = "1.3.0" -source = "git+https://github.com/Wodann/revm?rev=b25b44a#b25b44aac8132bd7086faf3bc3e5f79cbf0ded49" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "776848391ed76d5103ca1aa1632cd21b521e2870afb30b63723da862d69efd0f" dependencies = [ "revm-primitives", "serde", @@ -2970,31 +2894,32 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "2.2.0" -source = "git+https://github.com/Wodann/revm?rev=b25b44a#b25b44aac8132bd7086faf3bc3e5f79cbf0ded49" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3fd1856a7cb09197a02669d779e1afb5a627b0888a24814ba2b6a1ad4c3ff8d" dependencies = [ - "c-kzg", + "aurora-engine-modexp", "k256", - "num", "once_cell", "revm-primitives", "ripemd", - "secp256k1", "sha2", "substrate-bn", ] [[package]] name = "revm-primitives" -version = "1.3.0" -source = "git+https://github.com/Wodann/revm?rev=b25b44a#b25b44aac8132bd7086faf3bc3e5f79cbf0ded49" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4d7d3e793e907dc0797a9d3b43abfdf5226d133855214db9bd27d4cee33ebd" dependencies = [ - "alloy-primitives 0.4.2", - "alloy-rlp", + "alloy-primitives 0.6.3", "auto_impl", - "bitflags 2.4.1", + "bitflags 2.4.2", "bitvec", "c-kzg", + "cfg-if", + "dyn-clone", "enumn", "hashbrown 0.14.1", "hex", @@ -3078,12 +3003,6 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hex" version = "2.1.0" @@ -3114,7 +3033,7 @@ version = "0.38.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a74ee2d7c2581cd139b42447d7d9389b889bdaad3a73f1ebb16f2a3237bb19c" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", @@ -3207,24 +3126,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" -dependencies = [ - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" -dependencies = [ - "cc", -] - [[package]] name = "security-framework" version = "2.9.2" @@ -3298,7 +3199,6 @@ version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ - "indexmap 2.0.2", "itoa", "ryu", "serde", @@ -3371,6 +3271,16 @@ dependencies = [ "keccak", ] +[[package]] +name = "sha3-asm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac61da6b35ad76b195eb4771210f947734321a8d81d7738e1580d953bc7a15e" +dependencies = [ + "cc", + "cfg-if", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -3380,12 +3290,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shlex" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -4139,18 +4043,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - [[package]] name = "winapi" version = "0.3.9" diff --git a/crates/edr_eth/Cargo.toml b/crates/edr_eth/Cargo.toml index 581412a217..80b221699e 100644 --- a/crates/edr_eth/Cargo.toml +++ b/crates/edr_eth/Cargo.toml @@ -4,7 +4,7 @@ version = "0.2.0-dev" edition = "2021" [dependencies] -alloy-primitives = { version = "0.4", default-features = false, features = ["rand", "rlp"] } +alloy-primitives = { version = "0.6", default-features = false, features = ["rand", "rlp"] } alloy-rlp = { version = "0.3", default-features = false, features = ["derive"] } futures = {version = "0.3.28", default-features = false} hash-db = { version = "0.15.2", default-features = false } @@ -18,8 +18,7 @@ reqwest = { version = "0.11", features = ["blocking", "json"] } reqwest-middleware = { version = "0.2.4", default-features = false } reqwest-retry = { version = "0.3.0", default-features = false } reqwest-tracing = { version = "0.4.7", default-features = false, optional = true } -revm-primitives = { git = "https://github.com/Wodann/revm", rev = "b25b44a", version = "1.3", default-features = false } -# revm-primitives = { path = "../../../revm/crates/primitives", version = "1.3", default-features = false } +revm-primitives = { version = "3.0", default-features = false } serde = { version = "1.0.147", default-features = false, features = ["derive"], optional = true } serde_json = { version = "1.0.89", optional = true } sha3 = { version = "0.10.8", default-features = false } @@ -46,6 +45,6 @@ walkdir = { version = "2.3.3", default-features = false } [features] default = ["std"] serde = ["dep:serde", "alloy-primitives/serde", "revm-primitives/serde", "serde_json"] -std = ["alloy-primitives/std", "futures/std", "hash256-std-hasher/std", "hash-db/std", "hex/std", "itertools/use_std", "k256/std", "k256/precomputed-tables", "revm-primitives/std", "serde?/std", "sha3/std", "triehash/std", "uuid/std"] +std = ["alloy-primitives/std", "futures/std", "hash256-std-hasher/std", "hash-db/std", "hex/std", "itertools/use_std", "k256/std", "k256/precomputed-tables", "serde?/std", "sha3/std", "triehash/std", "uuid/std"] tracing = ["dep:tracing", "reqwest-tracing"] test-remote = ["serde"] diff --git a/crates/edr_eth/src/lib.rs b/crates/edr_eth/src/lib.rs index b9a0e785b0..55f37cd794 100644 --- a/crates/edr_eth/src/lib.rs +++ b/crates/edr_eth/src/lib.rs @@ -40,10 +40,8 @@ pub mod trie; pub mod utils; pub mod withdrawal; -pub use alloy_primitives::{ - hex_literal, Address, Bloom, BloomInput, Bytes, B256, B512, B64, U256, U64, -}; -pub use revm_primitives::{AccountInfo, HashMap, SpecId}; +pub use alloy_primitives::{Bloom, BloomInput, B512, B64, U64}; +pub use revm_primitives::{hex_literal, AccountInfo, Address, Bytes, HashMap, SpecId, B256, U256}; /// A secret key pub type Secret = B256; diff --git a/crates/edr_eth/src/log.rs b/crates/edr_eth/src/log.rs index 8fcce2b78c..c70d92434e 100644 --- a/crates/edr_eth/src/log.rs +++ b/crates/edr_eth/src/log.rs @@ -15,7 +15,7 @@ use crate::{Bloom, BloomInput}; pub fn add_log_to_bloom(log: &Log, bloom: &mut Bloom) { bloom.accrue(BloomInput::Raw(log.address.as_slice())); - log.topics + log.topics() .iter() .for_each(|topic| bloom.accrue(BloomInput::Raw(topic.as_slice()))); } diff --git a/crates/edr_eth/src/log/block.rs b/crates/edr_eth/src/log/block.rs index 3cb5fffd82..ec3b4baf8f 100644 --- a/crates/edr_eth/src/log/block.rs +++ b/crates/edr_eth/src/log/block.rs @@ -80,32 +80,28 @@ mod tests { use crate::{log::Log, Address, Bytes}; #[test] - fn test_block_log_full_serde() { + fn test_block_log_full_serde() -> anyhow::Result<()> { let log = BlockLog::Full(FullBlockLog { inner: ReceiptLog { - inner: Log { - address: Address::from_str("0000000000000000000000000000000000000011").unwrap(), - topics: vec![ + inner: Log::new_unchecked( + Address::from_str("0000000000000000000000000000000000000011")?, + vec![ B256::from_str( "000000000000000000000000000000000000000000000000000000000000dead", - ) - .unwrap(), + )?, B256::from_str( "000000000000000000000000000000000000000000000000000000000000beef", - ) - .unwrap(), + )?, ], - data: Bytes::from(hex::decode("0100ff").unwrap()), - }, + Bytes::from(hex::decode("0100ff")?), + ), transaction_hash: B256::from_str( "0xc008e9f9bb92057dd0035496fbf4fb54f66b4b18b370928e46d6603933054d5a", - ) - .expect("failed to parse hash from string"), + )?, }, block_hash: B256::from_str( "0x88fadbb673928c61b9ede3694ae0589ac77ae38ec90a24a6e12e83f42f18c7e8", - ) - .unwrap(), + )?, block_number: 0xa74fde, log_index: 0x653b, transaction_index: 0x1f, @@ -115,34 +111,35 @@ mod tests { let deserialized: BlockLog = serde_json::from_str(&serialized).unwrap(); assert_eq!(log, deserialized); + + Ok(()) } #[test] - fn test_block_log_partial_serde() { + fn test_block_log_partial_serde() -> anyhow::Result<()> { let log = BlockLog::Partial(ReceiptLog { - inner: Log { - address: Address::from_str("0000000000000000000000000000000000000011").unwrap(), - topics: vec![ + inner: Log::new_unchecked( + Address::from_str("0000000000000000000000000000000000000011").unwrap(), + vec![ B256::from_str( "000000000000000000000000000000000000000000000000000000000000dead", - ) - .unwrap(), + )?, B256::from_str( "000000000000000000000000000000000000000000000000000000000000beef", - ) - .unwrap(), + )?, ], - data: Bytes::from(hex::decode("0100ff").unwrap()), - }, + Bytes::from(hex::decode("0100ff")?), + ), transaction_hash: B256::from_str( "0xc008e9f9bb92057dd0035496fbf4fb54f66b4b18b370928e46d6603933054d5a", - ) - .expect("failed to parse hash from string"), + )?, }); let serialized = serde_json::to_string(&log).unwrap(); let deserialized: BlockLog = serde_json::from_str(&serialized).unwrap(); assert_eq!(log, deserialized); + + Ok(()) } } diff --git a/crates/edr_eth/src/log/filter.rs b/crates/edr_eth/src/log/filter.rs index bca45349a2..e623960de5 100644 --- a/crates/edr_eth/src/log/filter.rs +++ b/crates/edr_eth/src/log/filter.rs @@ -45,25 +45,22 @@ mod tests { }; #[test] - fn test_filter_log_serde() { + fn test_filter_log_serde() -> anyhow::Result<()> { let log = FilterLog { inner: FullBlockLog { inner: ReceiptLog { - inner: Log { - address: Address::from_str("0000000000000000000000000000000000000011") - .unwrap(), - topics: vec![ + inner: Log::new_unchecked( + Address::from_str("0000000000000000000000000000000000000011")?, + vec![ B256::from_str( "000000000000000000000000000000000000000000000000000000000000dead", - ) - .unwrap(), + )?, B256::from_str( "000000000000000000000000000000000000000000000000000000000000beef", - ) - .unwrap(), + )?, ], - data: Bytes::from(hex::decode("0100ff").unwrap()), - }, + Bytes::from(hex::decode("0100ff")?), + ), transaction_hash: B256::from_str( "0xc008e9f9bb92057dd0035496fbf4fb54f66b4b18b370928e46d6603933054d5a", ) @@ -84,5 +81,7 @@ mod tests { let deserialized: FilterLog = serde_json::from_str(&serialized).unwrap(); assert_eq!(log, deserialized); + + Ok(()) } } diff --git a/crates/edr_eth/src/log/receipt.rs b/crates/edr_eth/src/log/receipt.rs index b3d2e93eb8..8fd2951dd6 100644 --- a/crates/edr_eth/src/log/receipt.rs +++ b/crates/edr_eth/src/log/receipt.rs @@ -42,31 +42,30 @@ mod tests { use crate::{log::Log, Address, Bytes}; #[test] - fn test_receipt_log_serde() { + fn test_receipt_log_serde() -> anyhow::Result<()> { let log = ReceiptLog { - inner: Log { - address: Address::from_str("0000000000000000000000000000000000000011").unwrap(), - topics: vec![ + inner: Log::new_unchecked( + Address::from_str("0000000000000000000000000000000000000011")?, + vec![ B256::from_str( "000000000000000000000000000000000000000000000000000000000000dead", - ) - .unwrap(), + )?, B256::from_str( "000000000000000000000000000000000000000000000000000000000000beef", - ) - .unwrap(), + )?, ], - data: Bytes::from(hex::decode("0100ff").unwrap()), - }, + Bytes::from(hex::decode("0100ff")?), + ), transaction_hash: B256::from_str( "0xc008e9f9bb92057dd0035496fbf4fb54f66b4b18b370928e46d6603933054d5a", - ) - .expect("failed to parse hash from string"), + )?, }; let serialized = serde_json::to_string(&log).unwrap(); let deserialized: ReceiptLog = serde_json::from_str(&serialized).unwrap(); assert_eq!(log, deserialized); + + Ok(()) } } diff --git a/crates/edr_eth/src/receipt.rs b/crates/edr_eth/src/receipt.rs index 56dfecef44..4d03d7b0bd 100644 --- a/crates/edr_eth/src/receipt.rs +++ b/crates/edr_eth/src/receipt.rs @@ -470,16 +470,8 @@ mod tests { cumulative_gas_used: 0xffff, logs_bloom: Bloom::random(), logs: vec![ - Log { - address: Address::random(), - topics: vec![B256::random(), B256::random()], - data: Bytes::new(), - }, - Log { - address: Address::random(), - topics: Vec::new(), - data: Bytes::from_static(b"test"), - }, + Log::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), + Log::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) ], data: $receipt_data, spec_id: SpecId::LATEST, @@ -565,12 +557,7 @@ mod tests { cumulative_gas_used: receipt.inner.inner.cumulative_gas_used, logs_bloom: receipt.inner.inner.logs_bloom, logs: receipt.inner.inner.logs.into_iter().map(|log| { - let log = log.inner.inner.inner.clone(); - Log { - address: log.address, - topics: log.topics, - data: log.data, - } + log.inner.inner.inner.clone() }).collect(), }; diff --git a/crates/edr_eth/src/remote/filter.rs b/crates/edr_eth/src/remote/filter.rs index 31b3f51999..1c7b3d69dd 100644 --- a/crates/edr_eth/src/remote/filter.rs +++ b/crates/edr_eth/src/remote/filter.rs @@ -83,8 +83,8 @@ impl From<&FilterLog> for LogOutput { block_hash: Some(value.block_hash), block_number: Some(value.inner.block_number), address: value.inner.address, - data: value.inner.data.clone(), - topics: value.inner.topics.clone(), + data: value.inner.data.data.clone(), + topics: value.inner.data.topics().to_vec(), } } } diff --git a/crates/edr_evm/Cargo.toml b/crates/edr_evm/Cargo.toml index 2257fa2c90..4aaaf40b70 100644 --- a/crates/edr_evm/Cargo.toml +++ b/crates/edr_evm/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" [dependencies] alloy-rlp = { version = "0.3", default-features = false, features = ["derive"] } async-rwlock = { version = "1.3.0", default-features = false } -auto_impl = { version = "1.0.1", default-features = false } +auto_impl = { version = "1.2", default-features = false } cita_trie = { git = "https://github.com/Wodann/cita-trie", rev = "60efef5", version = "4.0.0", default-features = false } dyn-clone = { version = "1.0.13", default-features = false } futures = { version = "0.3.28", features = ["alloc", "async-await", "executor", "std"] } @@ -21,8 +21,7 @@ once_cell = { version = "1.18.0", default-features = false, features = ["alloc", parking_lot = { version = "0.12.1", default-features = false } edr_defaults = { version = "0.2.0-dev", path = "../edr_defaults" } edr_eth = { version = "0.2.0-dev", path = "../edr_eth", features = ["serde"] } -revm = { git = "https://github.com/Wodann/revm", rev = "b25b44a", version = "3.5", default-features = false, features = ["dev", "serde", "std"] } -# revm = { path = "../../../revm/crates/revm", version = "3.5", default-features = false, features = ["dev", "serde", "std"] } +revm = { version = "7.1", default-features = false, features = ["dev", "serde"] } serde = { version = "1.0.158", default-features = false, features = ["std"] } serde_json = { version = "1.0.94", default-features = false, features = ["std"] } thiserror = { version = "1.0.38", default-features = false } diff --git a/crates/edr_evm/src/block.rs b/crates/edr_evm/src/block.rs index 342de717ee..2abdeec27e 100644 --- a/crates/edr_evm/src/block.rs +++ b/crates/edr_evm/src/block.rs @@ -8,7 +8,10 @@ use auto_impl::auto_impl; use edr_eth::{block, receipt::BlockReceipt, remote::eth, withdrawal::Withdrawal, B256, U256}; pub use self::{ - builder::{BlockBuilder, BlockBuilderCreationError, BlockTransactionError, BuildBlockResult}, + builder::{ + BlockBuilder, BlockBuilderCreationError, BlockTransactionError, BuildBlockResult, + ExecutionResultWithContext, + }, local::LocalBlock, remote::{CreationError as RemoteBlockCreationError, RemoteBlock}, }; diff --git a/crates/edr_evm/src/block/builder.rs b/crates/edr_evm/src/block/builder.rs index c659dfa23b..6057c0af6c 100644 --- a/crates/edr_evm/src/block/builder.rs +++ b/crates/edr_evm/src/block/builder.rs @@ -13,18 +13,19 @@ use edr_eth::{ Address, Bloom, U256, }; use revm::{ - db::DatabaseComponentError, + db::{DatabaseComponentError, DatabaseComponents, StateRef}, primitives::{ - BlobExcessGasAndPrice, BlockEnv, CfgEnv, EVMError, ExecutionResult, InvalidHeader, - InvalidTransaction, Output, ResultAndState, SpecId, + BlobExcessGasAndPrice, BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, + ExecutionResult, InvalidHeader, InvalidTransaction, Output, ResultAndState, SpecId, }, + Context, DatabaseCommit, Evm, InnerEvmContext, }; use super::local::LocalBlock; use crate::{ blockchain::SyncBlockchain, - evm::{build_evm, run_transaction, SyncInspector}, - state::{AccountModifierFn, StateDiff, SyncState}, + debug::{DebugContext, EvmContext}, + state::{AccountModifierFn, StateDebug, StateDiff, SyncState}, ExecutableTransaction, }; @@ -47,6 +48,9 @@ pub enum BlockTransactionError { /// Blockchain errors #[error(transparent)] BlockHash(BE), + /// Custom error + #[error("{0}")] + Custom(String), /// Transaction has higher gas limit than is remaining in block #[error("Transaction has a higher gas limit than the remaining gas in the block")] ExceedsBlockGasLimit, @@ -76,8 +80,8 @@ where EVMError::Transaction(e) => match e { InvalidTransaction::LackOfFundForMaxFee { fee, balance } => { Self::InsufficientFunds { - max_upfront_cost: U256::from(fee), - sender_balance: balance, + max_upfront_cost: *fee, + sender_balance: *balance, } } _ => Self::InvalidTransaction(e), @@ -87,10 +91,26 @@ where ) => unreachable!("error: {error:?}"), EVMError::Database(DatabaseComponentError::State(e)) => Self::State(e), EVMError::Database(DatabaseComponentError::BlockHash(e)) => Self::BlockHash(e), + EVMError::Custom(error) => Self::Custom(error), } } } +/// The result of executing a transaction, along with the context in which it +/// was executed. +pub struct ExecutionResultWithContext< + 'evm, + BlockchainErrorT, + StateErrorT, + DebugDataT, + StateT: StateRef, +> { + /// The result of executing the transaction. + pub result: Result>, + /// The context in which the transaction was executed. + pub evm_context: EvmContext<'evm, BlockchainErrorT, DebugDataT, StateT>, +} + /// The result of building a block, using the [`BlockBuilder`]. pub struct BuildBlockResult { /// Built block @@ -101,7 +121,7 @@ pub struct BuildBlockResult { /// A builder for constructing Ethereum blocks. pub struct BlockBuilder { - cfg: CfgEnv, + cfg: CfgEnvWithHandlerCfg, header: PartialHeader, transactions: Vec, state_diff: StateDiff, @@ -114,13 +134,15 @@ impl BlockBuilder { /// Creates an intance of [`BlockBuilder`]. #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] pub fn new( - cfg: CfgEnv, + cfg: CfgEnvWithHandlerCfg, parent: &Header, mut options: BlockOptions, dao_hardfork_activation_block: Option, ) -> Result { - if cfg.spec_id < SpecId::BYZANTIUM { - return Err(BlockBuilderCreationError::UnsupportedHardfork(cfg.spec_id)); + if cfg.handler_cfg.spec_id < SpecId::BYZANTIUM { + return Err(BlockBuilderCreationError::UnsupportedHardfork( + cfg.handler_cfg.spec_id, + )); } let parent_gas_limit = if options.gas_limit.is_none() { @@ -130,20 +152,20 @@ impl BlockBuilder { }; let withdrawals = std::mem::take(&mut options.withdrawals).or_else(|| { - if cfg.spec_id >= SpecId::SHANGHAI { + if cfg.handler_cfg.spec_id >= SpecId::SHANGHAI { Some(Vec::new()) } else { None } }); - let header = PartialHeader::new(cfg.spec_id, options, Some(parent)); + let header = PartialHeader::new(cfg.handler_cfg.spec_id, options, Some(parent)); if let Some(dao_hardfork_activation_block) = dao_hardfork_activation_block { const DAO_FORCE_EXTRA_DATA_RANGE: u64 = 9; let drift = header.number - dao_hardfork_activation_block; - if cfg.spec_id >= SpecId::DAO_FORK + if cfg.handler_cfg.spec_id >= SpecId::DAO_FORK && drift <= DAO_FORCE_EXTRA_DATA_RANGE && *header.extra_data != DAO_EXTRA_DATA { @@ -163,7 +185,7 @@ impl BlockBuilder { } /// Retrieves the config of the block builder. - pub fn config(&self) -> &CfgEnv { + pub fn config(&self) -> &CfgEnvWithHandlerCfg { &self.cfg } @@ -184,23 +206,33 @@ impl BlockBuilder { /// Adds a pending transaction to #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] - pub fn add_transaction( + pub fn add_transaction<'blockchain, 'evm, BlockchainErrorT, DebugDataT, StateT, StateErrorT>( &mut self, - blockchain: &dyn SyncBlockchain, - state: &mut dyn SyncState, + blockchain: &'blockchain dyn SyncBlockchain, + state: StateT, transaction: ExecutableTransaction, - inspector: Option<&mut dyn SyncInspector>, - ) -> Result> + debug_context: Option>, + ) -> ExecutionResultWithContext<'evm, BlockchainErrorT, StateErrorT, DebugDataT, StateT> where + 'blockchain: 'evm, BlockchainErrorT: Debug + Send, + StateT: StateRef + DatabaseCommit + StateDebug, StateErrorT: Debug + Send, { // transaction's gas limit cannot be greater than the remaining gas in the // block if transaction.gas_limit() > self.gas_remaining() { - return Err(BlockTransactionError::ExceedsBlockGasLimit); + return ExecutionResultWithContext { + result: Err(BlockTransactionError::ExceedsBlockGasLimit), + evm_context: EvmContext { + debug: debug_context, + state, + }, + }; } + let spec_id = self.cfg.handler_cfg.spec_id; + let block = BlockEnv { number: U256::from(self.header.number), coinbase: self.header.beneficiary, @@ -208,7 +240,7 @@ impl BlockBuilder { difficulty: self.header.difficulty, basefee: self.header.base_fee.unwrap_or(U256::ZERO), gas_limit: U256::from(self.header.gas_limit), - prevrandao: if self.cfg.spec_id >= SpecId::MERGE { + prevrandao: if spec_id >= SpecId::MERGE { Some(self.header.mix_hash) } else { None @@ -220,18 +252,93 @@ impl BlockBuilder { .map(|BlobGas { excess_gas, .. }| BlobExcessGasAndPrice::new(*excess_gas)), }; - let evm = build_evm( - blockchain, - &state, + let env = EnvWithHandlerCfg::new_with_cfg_env( self.cfg.clone(), - transaction.clone().into(), block.clone(), + transaction.clone().into(), ); - let ResultAndState { - result, - state: state_diff, - } = run_transaction(evm, inspector)?; + let db = DatabaseComponents { + state, + block_hash: blockchain, + }; + + let ( + mut evm_context, + ResultAndState { + result, + state: state_diff, + }, + ) = { + if let Some(debug_context) = debug_context { + let mut evm = Evm::builder() + .with_ref_db(db) + .with_external_context(debug_context.data) + .with_env_with_handler_cfg(env) + .append_handler_register(debug_context.register_handles_fn) + .build(); + + let result = evm.transact(); + let Context { + evm: + revm::EvmContext { + inner: InnerEvmContext { db, .. }, + .. + }, + external, + } = evm.into_context(); + + let evm_context = EvmContext { + debug: Some(DebugContext { + data: external, + register_handles_fn: debug_context.register_handles_fn, + }), + state: db.0.state, + }; + + match result { + Ok(result) => (evm_context, result), + Err(error) => { + return ExecutionResultWithContext { + result: Err(error.into()), + evm_context, + }; + } + } + } else { + let mut evm = Evm::builder() + .with_ref_db(db) + .with_env_with_handler_cfg(env) + .build(); + + let result = evm.transact(); + let Context { + evm: + revm::EvmContext { + inner: InnerEvmContext { db, .. }, + .. + }, + .. + } = evm.into_context(); + + let evm_context = EvmContext { + debug: None, + state: db.0.state, + }; + + match result { + Ok(result) => (evm_context, result), + Err(error) => { + return ExecutionResultWithContext { + result: Err(error.into()), + evm_context, + }; + } + } + } + }; + + let state = &mut evm_context.state; self.state_diff.apply_diff(state_diff.clone()); @@ -239,7 +346,7 @@ impl BlockBuilder { self.header.gas_used += result.gas_used(); - let logs: Vec = result.logs().into_iter().map(Log::from).collect(); + let logs = result.logs().to_vec(); let logs_bloom = { let mut bloom = Bloom::ZERO; for log in &logs { @@ -260,7 +367,7 @@ impl BlockBuilder { }; let gas_price = transaction.gas_price(); - let effective_gas_price = if blockchain.spec_id() >= SpecId::LONDON { + let effective_gas_price = if spec_id >= SpecId::LONDON { if let SignedTransaction::Eip1559(transaction) = &*transaction { block.basefee + (gas_price - block.basefee).min(transaction.max_priority_fee_per_gas) @@ -279,7 +386,7 @@ impl BlockBuilder { data: match &*transaction { SignedTransaction::PreEip155Legacy(_) | SignedTransaction::PostEip155Legacy(_) => { - if self.cfg.spec_id < SpecId::BYZANTIUM { + if spec_id < SpecId::BYZANTIUM { TypedReceiptData::PreEip658Legacy { state_root: state .state_root() @@ -293,7 +400,7 @@ impl BlockBuilder { SignedTransaction::Eip1559(_) => TypedReceiptData::Eip1559 { status }, SignedTransaction::Eip4844(_) => TypedReceiptData::Eip4844 { status }, }, - spec_id: self.cfg.spec_id, + spec_id, }, transaction_hash: *transaction.hash(), transaction_index: self.transactions.len() as u64, @@ -307,7 +414,10 @@ impl BlockBuilder { self.transactions.push(transaction); - Ok(result) + ExecutionResultWithContext { + result: Ok(result), + evm_context, + } } /// Finalizes the block, returning the block and the callers of the @@ -387,6 +497,7 @@ impl BlockBuilder { #[cfg(test)] mod tests { use edr_eth::Bytes; + use revm::primitives::CfgEnv; #[test] fn dao_hardfork_has_extra_data() { @@ -402,9 +513,7 @@ mod tests { ..Header::default() }; - let mut cfg = CfgEnv::default(); - cfg.spec_id = SpecId::BYZANTIUM; - + let cfg = CfgEnvWithHandlerCfg::new_with_spec_id(CfgEnv::default(), SpecId::BYZANTIUM); let block_options = BlockOptions { number: Some(DUMMY_DAO_HARDFORK_BLOCK_NUMBER), extra_data: Some(Bytes::from(DAO_EXTRA_DATA)), @@ -434,8 +543,7 @@ mod tests { ..Header::default() }; - let mut cfg = CfgEnv::default(); - cfg.spec_id = SpecId::BYZANTIUM; + let cfg = CfgEnvWithHandlerCfg::new_with_spec_id(CfgEnv::default(), SpecId::BYZANTIUM); let block_options = BlockOptions { number: Some(DUMMY_DAO_HARDFORK_BLOCK_NUMBER), diff --git a/crates/edr_evm/src/blockchain.rs b/crates/edr_evm/src/blockchain.rs index b9fee2eaae..70b04868f3 100644 --- a/crates/edr_evm/src/blockchain.rs +++ b/crates/edr_evm/src/blockchain.rs @@ -6,6 +6,7 @@ pub mod storage; use std::{collections::BTreeMap, fmt::Debug, ops::Bound::Included, sync::Arc}; +use auto_impl::auto_impl; use edr_eth::{ log::FilterLog, receipt::BlockReceipt, spec::HardforkActivations, Address, B256, U256, }; @@ -78,6 +79,7 @@ pub enum BlockchainError { } /// Trait for implementations of an Ethereum blockchain. +#[auto_impl(&)] pub trait Blockchain { /// The blockchain's error type type BlockchainError; diff --git a/crates/edr_evm/src/blockchain/storage/sparse.rs b/crates/edr_evm/src/blockchain/storage/sparse.rs index 0378f6463a..00c98fc172 100644 --- a/crates/edr_evm/src/blockchain/storage/sparse.rs +++ b/crates/edr_evm/src/blockchain/storage/sparse.rs @@ -236,7 +236,7 @@ pub fn logs( for receipt in receipts { let filtered_logs = receipt.logs.iter().filter(|log| { matches_address_filter(&log.address, &addresses) - && matches_topics_filter(&log.topics, topics_filter) + && matches_topics_filter(log.topics(), topics_filter) }); logs.extend(filtered_logs.cloned()); diff --git a/crates/edr_evm/src/db.rs b/crates/edr_evm/src/db.rs deleted file mode 100644 index 1d88847442..0000000000 --- a/crates/edr_evm/src/db.rs +++ /dev/null @@ -1,12 +0,0 @@ -mod fork; -mod layered_db; -mod remote; -mod request; -mod sync; - -pub use sync::{AsyncDatabase, SyncDatabase}; - -pub use fork::{ForkDatabase, ForkDatabaseError}; -pub use layered_db::{EdrLayer, LayeredDatabase}; - -pub use remote::RemoteDatabase; diff --git a/crates/edr_evm/src/debug.rs b/crates/edr_evm/src/debug.rs new file mode 100644 index 0000000000..7105449e74 --- /dev/null +++ b/crates/edr_evm/src/debug.rs @@ -0,0 +1,37 @@ +use auto_impl::auto_impl; +use revm::db::{DatabaseComponents, StateRef, WrapDatabaseRef}; + +use crate::blockchain::SyncBlockchain; + +/// Type for registering handles, specialised for EDR database component types. +pub type HandleRegister<'evm, BlockchainErrorT, DebugDataT, StateT> = + revm::handler::register::HandleRegister< + DebugDataT, + WrapDatabaseRef< + DatabaseComponents< + StateT, + &'evm dyn SyncBlockchain::Error>, + >, + >, + >; + +/// Type for encapsulating contextual data and handler registration in an +/// `EvmBuilder`. +pub struct DebugContext<'evm, BlockchainErrorT, DebugDataT, StateT: StateRef> { + /// The contextual data. + pub data: DebugDataT, + /// The function to register handles. + pub register_handles_fn: HandleRegister<'evm, BlockchainErrorT, DebugDataT, StateT>, +} + +pub struct EvmContext<'evm, BlockchainErrorT, DebugDataT, StateT: StateRef> { + pub debug: Option>, + pub state: StateT, +} + +/// Trait for getting contextual data. +#[auto_impl(&mut)] +pub trait GetContextData { + /// Retrieves the contextual data. + fn get_context_data(&mut self) -> &mut DataT; +} diff --git a/crates/edr_evm/src/debug_trace.rs b/crates/edr_evm/src/debug_trace.rs index 8693ccd187..3de5d2d38c 100644 --- a/crates/edr_evm/src/debug_trace.rs +++ b/crates/edr_evm/src/debug_trace.rs @@ -1,17 +1,22 @@ -use std::{collections::HashMap, fmt::Debug}; +use std::{collections::HashMap, fmt::Debug, sync::Arc}; use edr_eth::{signature::SignatureError, utils::u256_to_padded_hex, B256}; use revm::{ - inspectors::GasInspector, - interpreter::{opcode, CallInputs, CreateInputs, Gas, InstructionResult, Interpreter, Stack}, + db::DatabaseComponents, + handler::register::EvmHandler, + interpreter::{ + opcode::{self, BoxedInstruction, InstructionTables}, + InstructionResult, Interpreter, InterpreterResult, + }, primitives::{ - hex, Address, BlockEnv, Bytes, CfgEnv, ExecutionResult, ResultAndState, SpecId, U256, + hex, Address, BlockEnv, Bytes, CfgEnvWithHandlerCfg, ExecutionResult, ResultAndState, + SpecId, U256, }, - EVMData, Inspector, JournalEntry, + Database, Evm, EvmContext, JournalEntry, }; use crate::{ - blockchain::SyncBlockchain, evm::build_evm, state::SyncState, ExecutableTransaction, + blockchain::SyncBlockchain, debug::GetContextData, state::SyncState, ExecutableTransaction, TransactionError, }; @@ -21,7 +26,7 @@ pub fn debug_trace_transaction( blockchain: &dyn SyncBlockchain, // Take ownership of the state so that we can apply throw-away modifications on it mut state: Box>, - evm_config: CfgEnv, + evm_config: CfgEnvWithHandlerCfg, trace_config: DebugTraceConfig, block_env: BlockEnv, transactions: Vec, @@ -31,45 +36,53 @@ where BlockchainErrorT: Debug + Send, StateErrorT: Debug + Send, { - if evm_config.spec_id < SpecId::SPURIOUS_DRAGON { + if evm_config.handler_cfg.spec_id < SpecId::SPURIOUS_DRAGON { // Matching Hardhat Network behaviour: https://github.com/NomicFoundation/hardhat/blob/af7e4ce6a18601ec9cd6d4aa335fa7e24450e638/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts#L427 return Err(DebugTraceError::InvalidSpecId { - spec_id: evm_config.spec_id, + spec_id: evm_config.handler_cfg.spec_id, }); } - if evm_config.spec_id > SpecId::MERGE && block_env.prevrandao.is_none() { + if evm_config.handler_cfg.spec_id > SpecId::MERGE && block_env.prevrandao.is_none() { return Err(TransactionError::MissingPrevrandao.into()); } for transaction in transactions { if transaction.hash() == transaction_hash { - let evm = build_evm( - blockchain, - &state, - evm_config, - transaction.into(), - block_env, - ); let mut tracer = TracerEip3155::new(trace_config); - let ResultAndState { - result: execution_result, - .. - } = evm - .inspect_ref(&mut tracer) - .map_err(TransactionError::from)?; - - return Ok(execution_result_to_debug_result(execution_result, tracer)); + + let ResultAndState { result, .. } = { + let mut evm = Evm::builder() + .with_ref_db(DatabaseComponents { + state: state.as_ref(), + block_hash: blockchain, + }) + .with_external_context(&mut tracer) + .with_cfg_env_with_handler_cfg(evm_config) + .append_handler_register(register_eip_3155_tracer_handles) + .with_block_env(block_env) + .with_tx_env(transaction.into()) + .build(); + + evm.transact().map_err(TransactionError::from)? + }; + + return Ok(execution_result_to_debug_result(result, tracer)); } else { - let evm = build_evm( - blockchain, - &state, - evm_config.clone(), - transaction.into(), - block_env.clone(), - ); - let ResultAndState { state: changes, .. } = - evm.transact_ref().map_err(TransactionError::from)?; + let ResultAndState { state: changes, .. } = { + let mut evm = Evm::builder() + .with_ref_db(DatabaseComponents { + state: state.as_ref(), + block_hash: blockchain, + }) + .with_cfg_env_with_handler_cfg(evm_config.clone()) + .with_block_env(block_env.clone()) + .with_tx_env(transaction.into()) + .build(); + + evm.transact().map_err(TransactionError::from)? + }; + state.commit(changes); } } @@ -196,22 +209,101 @@ pub struct DebugTraceLogItem { pub storage: Option>, } +/// Register EIP-3155 tracer handles. +pub fn register_eip_3155_tracer_handles< + DatabaseT: Database, + ContextT: GetContextData, +>( + handler: &mut EvmHandler<'_, ContextT, DatabaseT>, +) { + // Every instruction inside flat table that is going to be wrapped by tracer + // calls. + let table = handler + .instruction_table + .take() + .expect("Handler must have instruction table"); + + let table = match table { + InstructionTables::Plain(table) => table + .into_iter() + .map(|i| instruction_handler(i)) + .collect::>(), + InstructionTables::Boxed(table) => table + .into_iter() + .map(|i| instruction_handler(i)) + .collect::>(), + }; + + // cast vector to array. + handler.instruction_table = Some(InstructionTables::Boxed( + table.try_into().unwrap_or_else(|_| unreachable!()), + )); + + // call outcome + let old_handle = handler.execution.insert_call_outcome.clone(); + handler.execution.insert_call_outcome = Arc::new(move |ctx, frame, shared_memory, outcome| { + let tracer = ctx.external.get_context_data(); + tracer.on_inner_frame_result(&outcome.result); + + old_handle(ctx, frame, shared_memory, outcome) + }); + + // create outcome + let old_handle = handler.execution.insert_create_outcome.clone(); + handler.execution.insert_create_outcome = Arc::new(move |ctx, frame, outcome| { + let tracer = ctx.external.get_context_data(); + tracer.on_inner_frame_result(&outcome.result); + + old_handle(ctx, frame, outcome) + }); +} + +/// Outer closure that calls tracer for every instruction. +fn instruction_handler< + 'a, + ContextT: GetContextData, + DatabaseT: Database, + Instruction: Fn(&mut Interpreter, &mut Evm<'a, ContextT, DatabaseT>) + 'a, +>( + instruction: Instruction, +) -> BoxedInstruction<'a, Evm<'a, ContextT, DatabaseT>> { + Box::new( + move |interpreter: &mut Interpreter, host: &mut Evm<'a, ContextT, DatabaseT>| { + // SAFETY: as the PC was already incremented we need to subtract 1 to preserve + // the old Inspector behavior. + interpreter.instruction_pointer = unsafe { interpreter.instruction_pointer.sub(1) }; + + host.context.external.get_context_data().step(interpreter); + if interpreter.instruction_result != InstructionResult::Continue { + return; + } + + // return PC to old value + interpreter.instruction_pointer = unsafe { interpreter.instruction_pointer.add(1) }; + + // execute instruction. + instruction(interpreter, host); + + host.context + .external + .get_context_data() + .step_end(interpreter, &mut host.context.evm); + }, + ) +} + /// An EIP-3155 compatible EVM tracer. -/// Based on [REVM TracerEip3155](https://github.com/bluealloy/revm/blob/70cf969a25a45e3bb4e503926297d61a90c7eec5/crates/revm/src/inspector/tracer_eip3155.rs). -/// Original licensed under the MIT license. #[derive(Debug)] pub struct TracerEip3155 { config: DebugTraceConfig, logs: Vec, - gas_inspector: GasInspector, contract_address: Address, gas_remaining: u64, memory: Vec, mem_size: usize, opcode: u8, pc: usize, - skip: bool, - stack: Stack, + stack: Vec, // Contract-specific storage storage: HashMap>, } @@ -222,28 +314,48 @@ impl TracerEip3155 { Self { config, logs: Vec::default(), - gas_inspector: GasInspector::default(), contract_address: Address::default(), - stack: Stack::new(), + stack: Vec::new(), pc: 0, opcode: 0, gas_remaining: 0, memory: Vec::default(), mem_size: 0, - skip: false, storage: HashMap::default(), } } - fn record_log(&mut self, data: &mut EVMData<'_, DatabaseErrorT>) { - let depth = data.journaled_state.depth(); + fn step(&mut self, interp: &mut Interpreter) { + self.contract_address = interp.contract.address; + self.gas_remaining = interp.gas().remaining(); + + if !self.config.disable_stack { + self.stack = interp.stack.data().clone(); + } + + if !self.config.disable_memory { + self.memory = interp.shared_memory.context_memory().to_vec(); + } + + self.mem_size = interp.shared_memory.context_memory().len(); + + self.opcode = interp.current_opcode(); + + self.pc = interp.program_counter(); + } + + fn step_end( + &mut self, + interp: &mut Interpreter, + context: &mut EvmContext, + ) { + let depth = context.journaled_state.depth(); let stack = if self.config.disable_stack { None } else { Some( self.stack - .data() .iter() .map(u256_to_padded_hex) .collect::>(), @@ -260,9 +372,13 @@ impl TracerEip3155 { None } else { if matches!(self.opcode, opcode::SLOAD | opcode::SSTORE) { - let last_entry = data.journaled_state.journal.last().and_then(|v| v.last()); + let last_entry = context + .journaled_state + .journal + .last() + .and_then(|v| v.last()); if let Some(JournalEntry::StorageChange { address, key, .. }) = last_entry { - let value = data.journaled_state.state[address].storage[key].present_value(); + let value = context.journaled_state.state[address].storage[key].present_value(); let contract_storage = self.storage.entry(self.contract_address).or_default(); contract_storage.insert(u256_to_padded_hex(key), u256_to_padded_hex(&value)); } @@ -287,21 +403,7 @@ impl TracerEip3155 { String::from, ); - // We don't support gas computation for these opcodes yet - let gas_cost = if matches!( - self.opcode, - opcode::CREATE - | opcode::CREATE2 - | opcode::CALL - | opcode::CALLCODE - | opcode::DELEGATECALL - | opcode::STATICCALL - ) { - 0 - } else { - self.gas_inspector.last_gas_cost() - }; - + let gas_cost = self.gas_remaining.saturating_sub(interp.gas().remaining()); let log_item = DebugTraceLogItem { pc: self.pc as u64, op: self.opcode, @@ -317,112 +419,18 @@ impl TracerEip3155 { }; self.logs.push(log_item); } -} -impl Inspector for TracerEip3155 { - fn initialize_interp( - &mut self, - interp: &mut Interpreter, - data: &mut EVMData<'_, DatabaseErrorT>, - ) -> InstructionResult { - self.gas_inspector.initialize_interp(interp, data); - InstructionResult::Continue - } - - fn step( - &mut self, - interp: &mut Interpreter, - data: &mut EVMData<'_, DatabaseErrorT>, - ) -> InstructionResult { - self.contract_address = interp.contract.address; - - self.gas_inspector.step(interp, data); - self.gas_remaining = self.gas_inspector.gas_remaining(); - - if !self.config.disable_stack { - self.stack = interp.stack.clone(); - } - - if !self.config.disable_memory { - self.memory = interp.memory.data().clone(); - } - - self.mem_size = interp.memory.len(); - - self.opcode = interp.current_opcode(); - - self.pc = interp.program_counter(); - - InstructionResult::Continue - } - - fn step_end( - &mut self, - interp: &mut Interpreter, - data: &mut EVMData<'_, DatabaseErrorT>, - eval: InstructionResult, - ) -> InstructionResult { - self.gas_inspector.step_end(interp, data, eval); - - // Omit extra return https://github.com/bluealloy/revm/pull/563 - if self.skip { - self.skip = false; - return InstructionResult::Continue; + fn on_inner_frame_result(&mut self, result: &InterpreterResult) { + self.gas_remaining = if result.result.is_error() { + 0 + } else { + result.gas.remaining() }; - - self.record_log(data); - InstructionResult::Continue - } - - fn call( - &mut self, - data: &mut EVMData<'_, DatabaseErrorT>, - _inputs: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { - self.record_log(data); - (InstructionResult::Continue, Gas::new(0), Bytes::new()) - } - - fn call_end( - &mut self, - data: &mut EVMData<'_, DatabaseErrorT>, - inputs: &CallInputs, - remaining_gas: Gas, - ret: InstructionResult, - out: Bytes, - ) -> (InstructionResult, Gas, Bytes) { - self.gas_inspector - .call_end(data, inputs, remaining_gas, ret, out.clone()); - self.skip = true; - (ret, remaining_gas, out) - } - - fn create( - &mut self, - data: &mut EVMData<'_, DatabaseErrorT>, - _inputs: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { - self.record_log(data); - ( - InstructionResult::Continue, - None, - Gas::new(0), - Bytes::default(), - ) } +} - fn create_end( - &mut self, - data: &mut EVMData<'_, DatabaseErrorT>, - inputs: &CreateInputs, - ret: InstructionResult, - address: Option
, - remaining_gas: Gas, - out: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { - self.gas_inspector - .create_end(data, inputs, ret, address, remaining_gas, out.clone()); - self.skip = true; - (ret, address, remaining_gas, out) +impl GetContextData for TracerEip3155 { + fn get_context_data(&mut self) -> &mut TracerEip3155 { + self } } diff --git a/crates/edr_evm/src/evm.rs b/crates/edr_evm/src/evm.rs deleted file mode 100644 index e617eaa046..0000000000 --- a/crates/edr_evm/src/evm.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::fmt::Debug; - -use revm::{ - db::{DatabaseComponentError, DatabaseComponents, StateRef}, - primitives::{BlockEnv, CfgEnv, EVMError, ResultAndState, TxEnv}, - Inspector, -}; - -use crate::{blockchain::SyncBlockchain, SyncDatabase}; - -/// Super trait for an inspector of an `AsyncDatabase` that's debuggable. -pub trait SyncInspector: Inspector> + Debug + Send -where - BE: Debug + Send, - SE: Debug + Send, -{ -} - -impl SyncInspector for I -where - I: Inspector> + Debug + Send, - BE: Debug + Send, - SE: Debug + Send, -{ -} - -/// Creates an evm from the provided database, config, transaction, and block. -#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] -pub fn build_evm<'b, 's, BlockchainErrorT, StateErrorT>( - blockchain: &'b dyn SyncBlockchain, - state: &'s dyn StateRef, - cfg: CfgEnv, - transaction: TxEnv, - block: BlockEnv, -) -> revm::EVM> -where - BlockchainErrorT: Debug + Send, - StateErrorT: Debug + Send, -{ - let mut evm = revm::EVM::new(); - evm.database(DatabaseComponents { - state, - block_hash: blockchain, - }); - evm.env.cfg = cfg; - evm.env.block = block; - evm.env.tx = transaction; - - evm -} - -#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] -pub fn run_transaction<'evm, 'inspector, BE, SE>( - evm: revm::EVM>, - inspector: Option<&mut dyn SyncInspector>, -) -> Result>> -where - BE: Debug + Send, - SE: Debug + Send, - 'evm: 'inspector, -{ - if let Some(inspector) = inspector { - evm.inspect_ref(inspector) - } else { - evm.transact_ref() - } -} diff --git a/crates/edr_evm/src/inspector.rs b/crates/edr_evm/src/inspector.rs deleted file mode 100644 index eb1b10b3b1..0000000000 --- a/crates/edr_evm/src/inspector.rs +++ /dev/null @@ -1,226 +0,0 @@ -use std::{fmt::Debug, marker::PhantomData}; - -use edr_eth::{Address, Bytes, B256, U256}; -use revm::{ - db::DatabaseComponentError, - interpreter::{CallInputs, CreateInputs, Gas, InstructionResult, Interpreter}, - EVMData, Inspector, -}; - -use crate::{ - evm::SyncInspector, - trace::{Trace, TraceCollector}, -}; - -// TODO: Improve this design by introducing a InspectorMut trait - -/// Inspector that allows two inspectors to operate side-by-side. The immutable -/// inspector runs first, followed by the mutable inspector. To ensure both -/// inspectors observe a valid state, you have to ensure that only the mutable -/// inspector modifies state. The returned values are solely determined by the -/// mutable inspector. -#[derive(Debug)] -pub struct DualInspector -where - A: Inspector, - B: Inspector, -{ - immutable: A, - mutable: B, - phantom: PhantomData, -} - -impl DualInspector -where - A: Inspector, - B: Inspector, -{ - /// Constructs a `DualInspector` from the provided inspectors. - pub fn new(immutable: A, mutable: B) -> Self { - Self { - immutable, - mutable, - phantom: PhantomData, - } - } - - /// Returns the two inspectors wrapped by the `DualInspector`. - pub fn into_parts(self) -> (A, B) { - (self.immutable, self.mutable) - } -} - -impl Inspector for DualInspector -where - A: Inspector, - B: Inspector, -{ - fn initialize_interp( - &mut self, - interp: &mut Interpreter, - data: &mut EVMData<'_, E>, - ) -> InstructionResult { - self.immutable.initialize_interp(interp, data); - self.mutable.initialize_interp(interp, data) - } - - fn step(&mut self, interp: &mut Interpreter, data: &mut EVMData<'_, E>) -> InstructionResult { - self.immutable.step(interp, data); - self.mutable.step(interp, data) - } - - fn log( - &mut self, - evm_data: &mut EVMData<'_, E>, - address: &Address, - topics: &[B256], - data: &Bytes, - ) { - self.immutable.log(evm_data, address, topics, data); - self.mutable.log(evm_data, address, topics, data); - } - - fn step_end( - &mut self, - interp: &mut Interpreter, - data: &mut EVMData<'_, E>, - eval: InstructionResult, - ) -> InstructionResult { - self.immutable.step_end(interp, data, eval); - self.mutable.step_end(interp, data, eval) - } - - fn call( - &mut self, - data: &mut EVMData<'_, E>, - inputs: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { - self.immutable.call(data, inputs); - self.mutable.call(data, inputs) - } - - fn call_end( - &mut self, - data: &mut EVMData<'_, E>, - inputs: &CallInputs, - remaining_gas: Gas, - ret: InstructionResult, - out: Bytes, - ) -> (InstructionResult, Gas, Bytes) { - self.immutable - .call_end(data, inputs, remaining_gas, ret, out.clone()); - self.mutable.call_end(data, inputs, remaining_gas, ret, out) - } - - fn create( - &mut self, - data: &mut EVMData<'_, E>, - inputs: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { - self.immutable.create(data, inputs); - self.mutable.create(data, inputs) - } - - fn create_end( - &mut self, - data: &mut EVMData<'_, E>, - inputs: &CreateInputs, - ret: InstructionResult, - address: Option
, - remaining_gas: Gas, - out: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { - self.immutable - .create_end(data, inputs, ret, address, remaining_gas, out.clone()); - self.mutable - .create_end(data, inputs, ret, address, remaining_gas, out) - } - - fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { - self.immutable.selfdestruct(contract, target, value); - self.mutable.selfdestruct(contract, target, value); - } -} - -/// Container for storing inspector and tracer. -pub enum InspectorContainer<'inspector, BlockchainErrorT, StateErrorT> -where - BlockchainErrorT: Debug, - StateErrorT: Debug, -{ - /// No inspector or tracer. - None, - /// Only a tracer. - Collector(TraceCollector), - /// Both a tracer and an inspector. - Dual( - DualInspector< - TraceCollector, - &'inspector mut dyn SyncInspector, - DatabaseComponentError, - >, - ), - /// Only an inspector. - Inspector(&'inspector mut dyn SyncInspector), -} - -impl<'inspector, BlockchainErrorT, StateErrorT> - InspectorContainer<'inspector, BlockchainErrorT, StateErrorT> -where - BlockchainErrorT: Debug + Send, - StateErrorT: Debug + Send, -{ - /// Constructs a new instance. - pub fn new( - with_trace: bool, - tracer: Option<&'inspector mut dyn SyncInspector>, - ) -> Self { - if with_trace { - if let Some(tracer) = tracer { - InspectorContainer::Dual(DualInspector::new(TraceCollector::default(), tracer)) - } else { - InspectorContainer::Collector(TraceCollector::default()) - } - } else if let Some(tracer) = tracer { - InspectorContainer::Inspector(tracer) - } else { - InspectorContainer::None - } - } - - /// Returns the inspector, if it exists. - pub fn as_dyn_inspector( - &mut self, - ) -> Option<&mut dyn SyncInspector> { - match self { - InspectorContainer::None => None, - InspectorContainer::Collector(c) => Some(c), - InspectorContainer::Dual(d) => Some(d), - InspectorContainer::Inspector(t) => Some(t), - } - } - - /// Returns the tracer, if it exists. - pub fn into_tracer(self) -> Option { - match self { - InspectorContainer::None | InspectorContainer::Inspector(_) => None, - InspectorContainer::Collector(c) => Some(c), - InspectorContainer::Dual(d) => Some(d.into_parts().0), - } - } - - /// Clears and returns the trace, if it exists. - pub fn clear_trace(&mut self) -> Option { - match self { - InspectorContainer::None | InspectorContainer::Inspector(_) => None, - InspectorContainer::Collector(collector) => { - let tracer = std::mem::take(collector); - Some(tracer.into_trace()) - } - InspectorContainer::Dual(dual) => { - let tracer = std::mem::take(&mut dual.immutable); - Some(tracer.into_trace()) - } - } - } -} diff --git a/crates/edr_evm/src/lib.rs b/crates/edr_evm/src/lib.rs index 94b1db9b0a..ffc11820dc 100644 --- a/crates/edr_evm/src/lib.rs +++ b/crates/edr_evm/src/lib.rs @@ -5,23 +5,16 @@ //! The EDR EVM exposes APIs for running and interacting with a multi-threaded //! Ethereum Virtual Machine (or EVM). -pub use revm::{ - interpreter::{ - instruction_result::SuccessOrHalt, opcode, return_revert, CallInputs, CreateInputs, Gas, - InstructionResult, Interpreter, OPCODE_JUMPMAP, - }, - primitives::*, - EVMData, Inspector, -}; +pub use revm::primitives::*; pub use crate::{ block::*, + debug::{DebugContext, GetContextData}, debug_trace::{ - debug_trace_transaction, execution_result_to_debug_result, DebugTraceConfig, - DebugTraceError, DebugTraceLogItem, DebugTraceResult, TracerEip3155, + debug_trace_transaction, execution_result_to_debug_result, + register_eip_3155_tracer_handles, DebugTraceConfig, DebugTraceError, DebugTraceLogItem, + DebugTraceResult, TracerEip3155, }, - evm::SyncInspector, - inspector::*, mempool::{MemPool, MemPoolAddTransactionError, OrderedTransaction}, miner::*, random::RandomHashGenerator, @@ -40,9 +33,8 @@ pub mod trace; mod block; pub(crate) mod collections; +mod debug; mod debug_trace; -pub(crate) mod evm; -mod inspector; /// Types for managing Ethereum mem pool pub mod mempool; mod miner; @@ -53,10 +45,20 @@ mod runtime; pub mod test_utils; mod transaction; -/// Types for managing Ethereum precompiles -pub mod precompile { +/// Types for interfacing with the evm +pub mod evm { pub use revm::{ - is_precompile, - precompile::{Precompiles, SpecId}, + handler::register::{EvmHandler, HandleRegister}, + FrameOrResult, FrameResult, }; } + +/// Types for interfacing with the interpreter +pub mod interpreter { + pub use revm::interpreter::*; +} + +/// Types for managing Ethereum precompiles +pub mod precompile { + pub use revm::precompile::{PrecompileSpecId, Precompiles}; +} diff --git a/crates/edr_evm/src/miner.rs b/crates/edr_evm/src/miner.rs index cd5c2bea23..1607050a11 100644 --- a/crates/edr_evm/src/miner.rs +++ b/crates/edr_evm/src/miner.rs @@ -1,19 +1,18 @@ use std::{cmp::Ordering, fmt::Debug, sync::Arc}; use edr_eth::{block::BlockOptions, U256}; -use revm::primitives::{CfgEnv, ExecutionResult, InvalidTransaction}; +use revm::primitives::{CfgEnvWithHandlerCfg, ExecutionResult, InvalidTransaction}; use serde::{Deserialize, Serialize}; use crate::{ block::BlockBuilderCreationError, blockchain::SyncBlockchain, - evm::SyncInspector, - inspector::InspectorContainer, + debug::DebugContext, mempool::OrderedTransaction, state::{StateDiff, SyncState}, trace::Trace, - BlockBuilder, BlockTransactionError, BuildBlockResult, ExecutableTransaction, LocalBlock, - MemPool, SyncBlock, + BlockBuilder, BlockTransactionError, BuildBlockResult, ExecutableTransaction, + ExecutionResultWithContext, LocalBlock, MemPool, SyncBlock, }; /// The result of mining a block, after having been committed to the blockchain. @@ -48,8 +47,6 @@ pub struct MineBlockResultAndState { pub state_diff: StateDiff, /// Transaction results pub transaction_results: Vec, - /// Transaction traces - pub transaction_traces: Vec, } /// The type of ordering to use when selecting blocks to mine. @@ -85,19 +82,22 @@ pub enum MineBlockError { /// Mines a block using as many transactions as can fit in it. #[allow(clippy::too_many_arguments)] #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] -pub fn mine_block( - blockchain: &dyn SyncBlockchain, +pub fn mine_block<'blockchain, 'evm, BlockchainErrorT, DebugDataT, StateErrorT>( + blockchain: &'blockchain dyn SyncBlockchain, mut state: Box>, mem_pool: &MemPool, - cfg: &CfgEnv, + cfg: &CfgEnvWithHandlerCfg, options: BlockOptions, min_gas_price: U256, mine_ordering: MineOrdering, reward: U256, dao_hardfork_activation_block: Option, - inspector: Option<&mut dyn SyncInspector>, + mut debug_context: Option< + DebugContext<'evm, BlockchainErrorT, DebugDataT, Box>>, + >, ) -> Result, MineBlockError> where + 'blockchain: 'evm, BlockchainErrorT: Debug + Send, StateErrorT: Debug + Send, { @@ -130,9 +130,7 @@ where }; let mut results = Vec::new(); - let mut traces = Vec::new(); - let mut container = InspectorContainer::new(true, inspector); while let Some(transaction) = pending_transactions.next() { if transaction.gas_price() < min_gas_price { pending_transactions.remove_caller(transaction.caller()); @@ -140,12 +138,12 @@ where } let caller = *transaction.caller(); - match block_builder.add_transaction( - blockchain, - &mut state, - transaction, - container.as_dyn_inspector(), - ) { + let ExecutionResultWithContext { + result, + evm_context, + } = block_builder.add_transaction(blockchain, state, transaction, debug_context); + + match result { Err( BlockTransactionError::ExceedsBlockGasLimit | BlockTransactionError::InvalidTransaction( @@ -153,14 +151,17 @@ where ), ) => { pending_transactions.remove_caller(&caller); + state = evm_context.state; + debug_context = evm_context.debug; continue; } - Err(e) => { - return Err(MineBlockError::BlockTransaction(e)); + Err(error) => { + return Err(MineBlockError::BlockTransaction(error)); } Ok(result) => { results.push(result); - traces.push(container.clear_trace().unwrap()); + state = evm_context.state; + debug_context = evm_context.debug; } } } @@ -176,7 +177,6 @@ where state, state_diff, transaction_results: results, - transaction_traces: traces, }) } diff --git a/crates/edr_evm/src/runtime.rs b/crates/edr_evm/src/runtime.rs index 1e87d8ccb6..9a6de06d64 100644 --- a/crates/edr_evm/src/runtime.rs +++ b/crates/edr_evm/src/runtime.rs @@ -2,12 +2,16 @@ use std::fmt::Debug; use revm::{ db::{DatabaseComponents, StateRef}, - primitives::{BlockEnv, CfgEnv, ExecutionResult, ResultAndState, SpecId, TxEnv}, + primitives::{ + BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, ResultAndState, SpecId, + TxEnv, + }, + DatabaseCommit, Evm, }; use crate::{ blockchain::SyncBlockchain, - evm::{build_evm, run_transaction, SyncInspector}, + debug::DebugContext, state::{StateOverrides, StateRefOverrider, SyncState}, transaction::TransactionError, }; @@ -19,54 +23,93 @@ pub type SyncDatabase<'blockchain, 'state, BlockchainErrorT, StateErrorT> = Data >; /// Runs a transaction without committing the state. -#[cfg_attr(feature = "tracing", tracing::instrument(skip(inspector)))] -pub fn dry_run( - blockchain: &dyn SyncBlockchain, - state: &dyn SyncState, - state_overrides: &StateOverrides, - cfg: CfgEnv, +#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] +pub fn dry_run<'blockchain, 'evm, 'overrides, 'state, DebugDataT, BlockchainErrorT, StateErrorT>( + blockchain: &'blockchain dyn SyncBlockchain, + state: &'state dyn SyncState, + state_overrides: &'overrides StateOverrides, + cfg: CfgEnvWithHandlerCfg, transaction: TxEnv, block: BlockEnv, - inspector: Option<&mut dyn SyncInspector>, + debug_context: Option< + DebugContext< + 'evm, + BlockchainErrorT, + DebugDataT, + StateRefOverrider<'overrides, &'evm dyn SyncState>, + >, + >, ) -> Result> where + 'blockchain: 'evm, + 'state: 'evm, BlockchainErrorT: Debug + Send, StateErrorT: Debug + Send, { - if cfg.spec_id > SpecId::MERGE && block.prevrandao.is_none() { - return Err(TransactionError::MissingPrevrandao); - } + validate_configuration(&cfg, &block, &transaction)?; - if transaction.gas_priority_fee.is_some() && cfg.spec_id < SpecId::LONDON { - return Err(TransactionError::Eip1559Unsupported); - } + let state_overrider = StateRefOverrider::new(state_overrides, state); + + let env = EnvWithHandlerCfg::new_with_cfg_env(cfg, block, transaction); + let result = { + let evm_builder = Evm::builder().with_ref_db(DatabaseComponents { + state: state_overrider, + block_hash: blockchain, + }); - let state_overrider = StateRefOverrider::new(state_overrides, &state); + if let Some(debug_context) = debug_context { + let mut evm = evm_builder + .with_external_context(debug_context.data) + .with_env_with_handler_cfg(env) + .append_handler_register(debug_context.register_handles_fn) + .build(); - let evm = build_evm(blockchain, &state_overrider, cfg, transaction, block); + evm.transact() + } else { + let mut evm = evm_builder.with_env_with_handler_cfg(env).build(); + evm.transact() + } + }; - run_transaction(evm, inspector).map_err(TransactionError::from) + result.map_err(TransactionError::from) } /// Runs a transaction without committing the state, while disabling balance /// checks and creating accounts for new addresses. -#[cfg_attr(feature = "tracing", tracing::instrument(skip(inspector)))] -pub fn guaranteed_dry_run( - blockchain: &dyn SyncBlockchain, - state: &dyn SyncState, - state_overrides: &StateOverrides, - mut cfg: CfgEnv, - transaction: TxEnv, +#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] +pub fn guaranteed_dry_run< + 'blockchain, + 'evm, + 'overrides, + 'state, + DebugDataT, + BlockchainErrorT, + StateErrorT, +>( + blockchain: &'blockchain dyn SyncBlockchain, + state: &'state dyn SyncState, + state_overrides: &'overrides StateOverrides, + mut cfg: CfgEnvWithHandlerCfg, + mut transaction: TxEnv, block: BlockEnv, - inspector: Option<&mut dyn SyncInspector>, + debug_context: Option< + DebugContext< + 'evm, + BlockchainErrorT, + DebugDataT, + StateRefOverrider<'overrides, &'evm dyn SyncState>, + >, + >, ) -> Result> where + 'blockchain: 'evm, + 'state: 'evm, BlockchainErrorT: Debug + Send, StateErrorT: Debug + Send, { cfg.disable_balance_check = true; cfg.disable_block_gas_limit = true; - cfg.disable_nonce_check = true; + transaction.nonce = None; dry_run( blockchain, state, @@ -74,38 +117,63 @@ where cfg, transaction, block, - inspector, + debug_context, ) } /// Runs a transaction, committing the state in the process. -#[cfg_attr(feature = "tracing", tracing::instrument(skip(inspector)))] -pub fn run( - blockchain: &dyn SyncBlockchain, - state: &mut dyn SyncState, - cfg: CfgEnv, +#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] +pub fn run<'blockchain, 'evm, BlockchainErrorT, DebugDataT, StateT>( + blockchain: &'blockchain dyn SyncBlockchain, + state: StateT, + cfg: CfgEnvWithHandlerCfg, transaction: TxEnv, block: BlockEnv, - inspector: Option<&mut dyn SyncInspector>, -) -> Result> + debug_context: Option>, +) -> Result> where + 'blockchain: 'evm, BlockchainErrorT: Debug + Send, - StateErrorT: Debug + Send, + StateT: StateRef + DatabaseCommit, + StateT::Error: Debug + Send, { - let ResultAndState { - result, - state: changes, - } = dry_run( - blockchain, + validate_configuration(&cfg, &block, &transaction)?; + + let env = EnvWithHandlerCfg::new_with_cfg_env(cfg, block, transaction); + let evm_builder = Evm::builder().with_ref_db(DatabaseComponents { state, - &StateOverrides::default(), - cfg, - transaction, - block, - inspector, - )?; + block_hash: blockchain, + }); + + let result = if let Some(debug_context) = debug_context { + let mut evm = evm_builder + .with_external_context(debug_context.data) + .with_env_with_handler_cfg(env) + .append_handler_register(debug_context.register_handles_fn) + .build(); + + evm.transact_commit() + } else { + let mut evm = evm_builder.with_env_with_handler_cfg(env).build(); - state.commit(changes); + evm.transact_commit() + }?; Ok(result) } + +fn validate_configuration( + cfg: &CfgEnvWithHandlerCfg, + block: &BlockEnv, + transaction: &TxEnv, +) -> Result<(), TransactionError> { + if cfg.handler_cfg.spec_id > SpecId::MERGE && block.prevrandao.is_none() { + return Err(TransactionError::MissingPrevrandao); + } + + if transaction.gas_priority_fee.is_some() && cfg.handler_cfg.spec_id < SpecId::LONDON { + return Err(TransactionError::Eip1559Unsupported); + } + + Ok(()) +} diff --git a/crates/edr_evm/src/state/debug.rs b/crates/edr_evm/src/state/debug.rs index 5eaf61b1b9..d4c7f03fa2 100644 --- a/crates/edr_evm/src/state/debug.rs +++ b/crates/edr_evm/src/state/debug.rs @@ -37,7 +37,7 @@ impl Deref for AccountModifierFn { } /// A trait for debug operation on a database. -#[auto_impl(Box)] +#[auto_impl(&mut, Box)] pub trait StateDebug { /// The state's error type. type Error; diff --git a/crates/edr_evm/src/state/overrides.rs b/crates/edr_evm/src/state/overrides.rs index 33dcc14200..a4798ddfea 100644 --- a/crates/edr_evm/src/state/overrides.rs +++ b/crates/edr_evm/src/state/overrides.rs @@ -1,3 +1,5 @@ +use std::fmt::Debug; + use edr_eth::{ account::KECCAK_EMPTY, remote::{AccountOverrideOptions, StateOverrideOptions}, @@ -247,34 +249,34 @@ impl TryFrom for StateOverrides { } /// A wrapper around a state ref object that applies overrides. -pub struct StateRefOverrider<'state, StateErrorT> { - overrides: &'state StateOverrides, - state: &'state dyn StateRef, +pub struct StateRefOverrider<'overrides, StateT> { + overrides: &'overrides StateOverrides, + state: StateT, } -impl<'state, StateErrorT> StateRefOverrider<'state, StateErrorT> { +impl<'overrides, StateT> StateRefOverrider<'overrides, StateT> { /// Creates a new state ref overrider. pub fn new( - overrides: &'state StateOverrides, - state: &'state dyn StateRef, - ) -> StateRefOverrider<'state, StateErrorT> { + overrides: &'overrides StateOverrides, + state: StateT, + ) -> StateRefOverrider<'overrides, StateT> { StateRefOverrider { overrides, state } } } -impl<'state, StateErrorT> StateRef for StateRefOverrider<'state, StateErrorT> { - type Error = StateErrorT; +impl<'state, StateT: StateRef> StateRef for StateRefOverrider<'state, StateT> { + type Error = StateT::Error; fn basic(&self, address: Address) -> Result, Self::Error> { - self.overrides.account_info(self.state, &address) + self.overrides.account_info(&self.state, &address) } fn code_by_hash(&self, code_hash: B256) -> Result { - self.overrides.code_by_hash(self.state, code_hash) + self.overrides.code_by_hash(&self.state, code_hash) } fn storage(&self, address: Address, index: U256) -> Result { self.overrides - .account_storage_at(self.state, &address, &index) + .account_storage_at(&self.state, &address, &index) } } diff --git a/crates/edr_evm/src/trace.rs b/crates/edr_evm/src/trace.rs index fd8f390fed..41b6538cb3 100644 --- a/crates/edr_evm/src/trace.rs +++ b/crates/edr_evm/src/trace.rs @@ -1,15 +1,157 @@ -use std::fmt::Debug; +use std::{cell::RefCell, fmt::Debug, rc::Rc, sync::Arc}; use edr_eth::{Address, Bytes, U256}; use revm::{ + handler::register::EvmHandler, interpreter::{ - instruction_result::SuccessOrHalt, opcode, return_revert, CallInputs, CreateInputs, Gas, - InstructionResult, Interpreter, + opcode::{self, BoxedInstruction, InstructionTables}, + return_revert, CallInputs, CallOutcome, CreateInputs, CreateOutcome, InstructionResult, + Interpreter, SuccessOrHalt, }, - primitives::{Bytecode, ExecutionResult, Output}, - EVMData, Inspector, + primitives::{Bytecode, EVMError, ExecutionResult, Output}, + Database, Evm, EvmContext, FrameOrResult, FrameResult, }; +use crate::debug::GetContextData; + +/// Registers trace collector handles to the EVM handler. +pub fn register_trace_collector_handles< + DatabaseT: Database, + ContextT: GetContextData, +>( + handler: &mut EvmHandler<'_, ContextT, DatabaseT>, +) where + DatabaseT::Error: Debug, +{ + // Every instruction inside flat table that is going to be wrapped by tracer + // calls. + let table = handler + .instruction_table + .take() + .expect("Handler must have instruction table"); + + let table = match table { + InstructionTables::Plain(table) => table + .into_iter() + .map(|i| instruction_handler(i)) + .collect::>(), + InstructionTables::Boxed(table) => table + .into_iter() + .map(|i| instruction_handler(i)) + .collect::>(), + }; + + // cast vector to array. + handler.instruction_table = Some(InstructionTables::Boxed( + table.try_into().unwrap_or_else(|_| unreachable!()), + )); + + // call and create input stack shared between handlers. They are used to share + // inputs in *_end Inspector calls. + let call_input_stack = Rc::>>::new(RefCell::new(Vec::new())); + let create_input_stack = Rc::>>::new(RefCell::new(Vec::new())); + + // Create handler + let create_input_stack_inner = create_input_stack.clone(); + let old_handle = handler.execution.create.clone(); + handler.execution.create = Arc::new( + move |ctx, inputs| -> Result> { + let tracer = ctx.external.get_context_data(); + tracer.create(&ctx.evm, &inputs); + + create_input_stack_inner.borrow_mut().push(inputs.clone()); + + old_handle(ctx, inputs) + }, + ); + + // Call handler + let call_input_stack_inner = call_input_stack.clone(); + let old_handle = handler.execution.call.clone(); + handler.execution.call = Arc::new( + move |ctx, inputs| -> Result> { + let tracer = ctx.external.get_context_data(); + tracer.call(&mut ctx.evm, &inputs); + + call_input_stack_inner.borrow_mut().push(inputs.clone()); + + old_handle(ctx, inputs) + }, + ); + + // call outcome + let call_input_stack_inner = call_input_stack.clone(); + let old_handle = handler.execution.insert_call_outcome.clone(); + handler.execution.insert_call_outcome = Arc::new( + move |ctx: &mut revm::Context, frame, shared_memory, outcome| { + let call_inputs = call_input_stack_inner.borrow_mut().pop().unwrap(); + + let tracer = ctx.external.get_context_data(); + tracer.call_end(&ctx.evm, &call_inputs, &outcome); + + old_handle(ctx, frame, shared_memory, outcome) + }, + ); + + // create outcome + let create_input_stack_inner = create_input_stack.clone(); + let old_handle = handler.execution.insert_create_outcome.clone(); + handler.execution.insert_create_outcome = Arc::new(move |ctx, frame, outcome| { + let create_inputs = create_input_stack_inner.borrow_mut().pop().unwrap(); + + let tracer = ctx.external.get_context_data(); + tracer.create_end(&ctx.evm, &create_inputs, &outcome); + + old_handle(ctx, frame, outcome) + }); + + // last frame outcome + let old_handle = handler.execution.last_frame_return.clone(); + handler.execution.last_frame_return = Arc::new(move |ctx, frame_result| { + let tracer = ctx.external.get_context_data(); + match frame_result { + FrameResult::Call(outcome) => { + let call_inputs = call_input_stack.borrow_mut().pop().unwrap(); + tracer.call_transaction_end(&ctx.evm, &call_inputs, outcome); + } + FrameResult::Create(outcome) => { + let create_inputs = create_input_stack.borrow_mut().pop().unwrap(); + tracer.create_transaction_end(&ctx.evm, &create_inputs, outcome); + } + } + old_handle(ctx, frame_result) + }); +} + +/// Outer closure that calls tracer for every instruction. +fn instruction_handler< + 'a, + ContextT: GetContextData, + DatabaseT: Database, + Instruction: Fn(&mut Interpreter, &mut Evm<'a, ContextT, DatabaseT>) + 'a, +>( + instruction: Instruction, +) -> BoxedInstruction<'a, Evm<'a, ContextT, DatabaseT>> { + Box::new( + move |interpreter: &mut Interpreter, host: &mut Evm<'a, ContextT, DatabaseT>| { + // SAFETY: as the PC was already incremented we need to subtract 1 to preserve + // the old Inspector behavior. + interpreter.instruction_pointer = unsafe { interpreter.instruction_pointer.sub(1) }; + + host.context + .external + .get_context_data() + .step(interpreter, &host.context.evm); + + // return PC to old value + interpreter.instruction_pointer = unsafe { interpreter.instruction_pointer.add(1) }; + + // execute instruction. + instruction(interpreter, host); + }, + ) +} + /// Stack tracing message #[derive(Clone, Debug)] pub enum TraceMessage { @@ -98,34 +240,43 @@ impl Trace { /// Object that gathers trace information during EVM execution and can be turned /// into a trace upon completion. -#[derive(Debug, Default)] +#[derive(Debug)] pub struct TraceCollector { - trace: Trace, + traces: Vec, pending_before: Option, + is_new_trace: bool, } impl TraceCollector { /// Converts the [`TraceCollector`] into its [`Trace`]. - pub fn into_trace(self) -> Trace { - self.trace + pub fn into_traces(self) -> Vec { + self.traces + } + + /// Returns the traces collected so far. + pub fn traces(&self) -> &[Trace] { + &self.traces + } + + fn current_trace_mut(&mut self) -> &mut Trace { + self.traces.last_mut().expect("Trace must have been added") } fn validate_before_message(&mut self) { if let Some(message) = self.pending_before.take() { - self.trace.add_before(message); + self.current_trace_mut().add_before(message); } } -} -impl Inspector for TraceCollector -where - DatabaseErrorT: Debug, -{ - fn call( - &mut self, - data: &mut EVMData<'_, DatabaseErrorT>, - inputs: &mut CallInputs, - ) -> (InstructionResult, Gas, edr_eth::Bytes) { + fn call(&mut self, data: &mut EvmContext, inputs: &CallInputs) + where + DatabaseT::Error: Debug, + { + if self.is_new_trace { + self.is_new_trace = false; + self.traces.push(Trace::default()); + } + self.validate_before_message(); // This needs to be split into two functions to avoid borrow checker issues @@ -164,30 +315,27 @@ where code_address: Some(inputs.context.code_address), code: Some(code), }); - - (InstructionResult::Continue, Gas::new(0), Bytes::default()) } - fn call_end( + fn call_end( &mut self, - data: &mut EVMData<'_, DatabaseErrorT>, + data: &EvmContext, _inputs: &CallInputs, - remaining_gas: Gas, - ret: InstructionResult, - out: Bytes, - ) -> (InstructionResult, Gas, Bytes) { - match ret { + outcome: &CallOutcome, + ) { + match outcome.instruction_result() { return_revert!() if self.pending_before.is_some() => { self.pending_before = None; - return (ret, remaining_gas, out); + return; } _ => (), } self.validate_before_message(); + let ret = *outcome.instruction_result(); let safe_ret = if ret == InstructionResult::CallTooDeep - || ret == InstructionResult::OutOfFund + || ret == InstructionResult::OutOfFunds || ret == InstructionResult::StateChangeDuringStaticCall { InstructionResult::Revert @@ -198,33 +346,34 @@ where let result = match safe_ret.into() { SuccessOrHalt::Success(reason) => ExecutionResult::Success { reason, - gas_used: remaining_gas.spend(), - gas_refunded: remaining_gas.refunded() as u64, + gas_used: outcome.gas().spend(), + gas_refunded: outcome.gas().refunded() as u64, logs: data.journaled_state.logs.clone(), - output: Output::Call(out.clone()), + output: Output::Call(outcome.output().clone()), }, SuccessOrHalt::Revert => ExecutionResult::Revert { - gas_used: remaining_gas.spend(), - output: out.clone(), + gas_used: outcome.gas().spend(), + output: outcome.output().clone(), }, SuccessOrHalt::Halt(reason) => ExecutionResult::Halt { reason, - gas_used: remaining_gas.limit(), + gas_used: outcome.gas().limit(), }, - SuccessOrHalt::InternalContinue => panic!("Internal error: {safe_ret:?}"), + SuccessOrHalt::InternalContinue | SuccessOrHalt::InternalCallOrCreate => { + panic!("Internal error: {safe_ret:?}") + } SuccessOrHalt::FatalExternalError => panic!("Fatal external error"), }; - self.trace.add_after(result); - - (ret, remaining_gas, out) + self.current_trace_mut().add_after(result); } - fn create( - &mut self, - data: &mut EVMData<'_, DatabaseErrorT>, - inputs: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { + fn create(&mut self, data: &EvmContext, inputs: &CreateInputs) { + if self.is_new_trace { + self.is_new_trace = false; + self.traces.push(Trace::default()); + } + self.validate_before_message(); self.pending_before = Some(BeforeMessage { @@ -237,28 +386,19 @@ where code_address: None, code: None, }); - - ( - InstructionResult::Continue, - None, - Gas::new(0), - Bytes::default(), - ) } - fn create_end( + fn create_end( &mut self, - data: &mut EVMData<'_, DatabaseErrorT>, + data: &EvmContext, _inputs: &CreateInputs, - ret: InstructionResult, - address: Option
, - remaining_gas: Gas, - out: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { + outcome: &CreateOutcome, + ) { self.validate_before_message(); + let ret = *outcome.instruction_result(); let safe_ret = - if ret == InstructionResult::CallTooDeep || ret == InstructionResult::OutOfFund { + if ret == InstructionResult::CallTooDeep || ret == InstructionResult::OutOfFunds { InstructionResult::Revert } else { ret @@ -267,33 +407,29 @@ where let result = match safe_ret.into() { SuccessOrHalt::Success(reason) => ExecutionResult::Success { reason, - gas_used: remaining_gas.spend(), - gas_refunded: remaining_gas.refunded() as u64, + gas_used: outcome.gas().spend(), + gas_refunded: outcome.gas().refunded() as u64, logs: data.journaled_state.logs.clone(), - output: Output::Create(out.clone(), address), + output: Output::Create(outcome.output().clone(), outcome.address), }, SuccessOrHalt::Revert => ExecutionResult::Revert { - gas_used: remaining_gas.spend(), - output: out.clone(), + gas_used: outcome.gas().spend(), + output: outcome.output().clone(), }, SuccessOrHalt::Halt(reason) => ExecutionResult::Halt { reason, - gas_used: remaining_gas.limit(), + gas_used: outcome.gas().limit(), }, - SuccessOrHalt::InternalContinue => panic!("Internal error: {safe_ret:?}"), + SuccessOrHalt::InternalContinue | SuccessOrHalt::InternalCallOrCreate => { + panic!("Internal error: {safe_ret:?}") + } SuccessOrHalt::FatalExternalError => panic!("Fatal external error"), }; - self.trace.add_after(result); - - (ret, address, remaining_gas, out) + self.current_trace_mut().add_after(result); } - fn step( - &mut self, - interp: &mut Interpreter, - data: &mut EVMData<'_, DatabaseErrorT>, - ) -> InstructionResult { + fn step(&mut self, interp: &Interpreter, data: &EvmContext) { // Skip the step let skip_step = self.pending_before.as_ref().map_or(false, |message| { message.code.is_some() && interp.current_opcode() == opcode::STOP @@ -302,14 +438,48 @@ where self.validate_before_message(); if !skip_step { - self.trace.add_step( + self.current_trace_mut().add_step( data.journaled_state.depth(), interp.program_counter(), interp.current_opcode(), interp.stack.data().last().cloned(), ); } + } + + fn call_transaction_end( + &mut self, + data: &EvmContext, + inputs: &CallInputs, + outcome: &CallOutcome, + ) { + self.is_new_trace = true; + self.call_end(data, inputs, outcome); + } + + fn create_transaction_end( + &mut self, + data: &EvmContext, + inputs: &CreateInputs, + outcome: &CreateOutcome, + ) { + self.is_new_trace = true; + self.create_end(data, inputs, outcome); + } +} + +impl Default for TraceCollector { + fn default() -> Self { + Self { + traces: Vec::new(), + pending_before: None, + is_new_trace: true, + } + } +} - InstructionResult::Continue +impl GetContextData for TraceCollector { + fn get_context_data(&mut self) -> &mut TraceCollector { + self } } diff --git a/crates/edr_evm/src/transaction.rs b/crates/edr_evm/src/transaction.rs index 79c324cf86..58614c0e0f 100644 --- a/crates/edr_evm/src/transaction.rs +++ b/crates/edr_evm/src/transaction.rs @@ -17,6 +17,9 @@ pub enum TransactionError { /// Blockchain errors #[error(transparent)] Blockchain(#[from] BE), + #[error("{0}")] + /// Custom errors + Custom(String), /// EIP-1559 is not supported #[error("Cannot run transaction: EIP 1559 is not activated.")] Eip1559Unsupported, @@ -45,6 +48,7 @@ where ) => unreachable!("error: {error:?}"), EVMError::Database(DatabaseComponentError::State(e)) => Self::State(e), EVMError::Database(DatabaseComponentError::BlockHash(e)) => Self::Blockchain(e), + EVMError::Custom(error) => Self::Custom(error), } } } diff --git a/crates/edr_evm/src/transaction/executable.rs b/crates/edr_evm/src/transaction/executable.rs index 4a379ee3bb..36fee21d5c 100644 --- a/crates/edr_evm/src/transaction/executable.rs +++ b/crates/edr_evm/src/transaction/executable.rs @@ -11,10 +11,10 @@ use edr_eth::{ Address, U256, }; use revm::{ - interpreter::gas::initial_tx_gas, + interpreter::gas::validate_initial_tx_gas, primitives::{ - BerlinSpec, ByzantiumSpec, CreateScheme, FrontierSpec, HomesteadSpec, IstanbulSpec, - LatestSpec, LondonSpec, MergeSpec, PetersburgSpec, ShanghaiSpec, SpecId, + BerlinSpec, ByzantiumSpec, CancunSpec, CreateScheme, FrontierSpec, HomesteadSpec, + IstanbulSpec, LatestSpec, LondonSpec, MergeSpec, PetersburgSpec, ShanghaiSpec, SpecId, SpuriousDragonSpec, TangerineSpec, TransactTo, TxEnv, }, }; @@ -412,69 +412,69 @@ fn initial_cost(spec_id: SpecId, transaction: &SignedTransaction) -> u64 { transaction.access_list().cloned().map(Into::into); match spec_id { - SpecId::FRONTIER | SpecId::FRONTIER_THAWING => initial_tx_gas::( + SpecId::FRONTIER | SpecId::FRONTIER_THAWING => validate_initial_tx_gas::( transaction.data(), transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), - SpecId::HOMESTEAD | SpecId::DAO_FORK => initial_tx_gas::( + SpecId::HOMESTEAD | SpecId::DAO_FORK => validate_initial_tx_gas::( transaction.data(), transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), - SpecId::TANGERINE => initial_tx_gas::( + SpecId::TANGERINE => validate_initial_tx_gas::( transaction.data(), transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), - SpecId::SPURIOUS_DRAGON => initial_tx_gas::( + SpecId::SPURIOUS_DRAGON => validate_initial_tx_gas::( transaction.data(), transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), - SpecId::BYZANTIUM => initial_tx_gas::( + SpecId::BYZANTIUM => validate_initial_tx_gas::( transaction.data(), transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), - SpecId::PETERSBURG | SpecId::CONSTANTINOPLE => initial_tx_gas::( + SpecId::PETERSBURG | SpecId::CONSTANTINOPLE => validate_initial_tx_gas::( transaction.data(), transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), - SpecId::ISTANBUL | SpecId::MUIR_GLACIER => initial_tx_gas::( + SpecId::ISTANBUL | SpecId::MUIR_GLACIER => validate_initial_tx_gas::( transaction.data(), transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), - SpecId::BERLIN => initial_tx_gas::( + SpecId::BERLIN => validate_initial_tx_gas::( transaction.data(), transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), SpecId::LONDON | SpecId::ARROW_GLACIER | SpecId::GRAY_GLACIER => { - initial_tx_gas::( + validate_initial_tx_gas::( transaction.data(), transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ) } - SpecId::MERGE => initial_tx_gas::( + SpecId::MERGE => validate_initial_tx_gas::( transaction.data(), transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), - SpecId::SHANGHAI => initial_tx_gas::( + SpecId::SHANGHAI => validate_initial_tx_gas::( transaction.data(), transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), - SpecId::CANCUN => initial_tx_gas::( + SpecId::CANCUN => validate_initial_tx_gas::( transaction.data(), transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), - SpecId::LATEST => initial_tx_gas::( + SpecId::LATEST => validate_initial_tx_gas::( transaction.data(), transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), diff --git a/crates/edr_evm/tests/blockchain.rs b/crates/edr_evm/tests/blockchain.rs index 5123f85017..966cdbc5b4 100644 --- a/crates/edr_evm/tests/blockchain.rs +++ b/crates/edr_evm/tests/blockchain.rs @@ -172,16 +172,8 @@ fn insert_dummy_block_with_transaction( cumulative_gas_used: GAS_USED, logs_bloom: Bloom::default(), logs: vec![ - Log { - address: Address::random(), - topics: Vec::new(), - data: Bytes::new(), - }, - Log { - address: Address::random(), - topics: Vec::new(), - data: Bytes::new(), - }, + Log::new_unchecked(Address::random(), Vec::new(), Bytes::new()), + Log::new_unchecked(Address::random(), Vec::new(), Bytes::new()), ], data: TypedReceiptData::PostEip658Legacy { status: 1 }, spec_id: blockchain.spec_id(), @@ -498,7 +490,7 @@ async fn logs_local() -> anyhow::Result<()> { for (log, filter_log) in expected.iter().zip(actual.iter()) { assert_eq!(log.address, filter_log.address); - assert_eq!(log.topics, filter_log.topics); + assert_eq!(log.topics(), filter_log.topics()); assert_eq!(log.data, filter_log.data); } } diff --git a/crates/edr_napi/index.d.ts b/crates/edr_napi/index.d.ts index 1a72ca428c..b3ac1df5c1 100644 --- a/crates/edr_napi/index.d.ts +++ b/crates/edr_napi/index.d.ts @@ -313,7 +313,7 @@ export const enum ExceptionalHalt { /** Error on created contract that begins with EF */ CreateContractStartingWithEF = 12, /** EIP-3860: Limit and meter initcode. Initcode size limit exceeded. */ - CreateInitcodeSizeLimit = 13 + CreateInitCodeSizeLimit = 13 } /** The result when the EVM terminates due to an exceptional halt. */ export interface HaltResult { diff --git a/crates/edr_napi/src/call_override.rs b/crates/edr_napi/src/call_override.rs index e684e957ff..dd598fdea6 100644 --- a/crates/edr_napi/src/call_override.rs +++ b/crates/edr_napi/src/call_override.rs @@ -24,7 +24,7 @@ impl TryCast> for Option Ok(None), Some(result) => Ok(Some(edr_provider::CallOverrideResult { - result: result.result.try_cast()?, + output: result.result.try_cast()?, should_revert: result.should_revert, })), } diff --git a/crates/edr_napi/src/log.rs b/crates/edr_napi/src/log.rs index 605220fc45..1ca7395073 100644 --- a/crates/edr_napi/src/log.rs +++ b/crates/edr_napi/src/log.rs @@ -14,12 +14,12 @@ pub struct ExecutionLog { impl ExecutionLog { pub fn new(env: &Env, log: &edr_evm::Log) -> napi::Result { let topics = log - .topics + .topics() .iter() .map(|topic| Buffer::from(topic.as_slice())) .collect(); - let data = log.data.clone(); + let data = log.data.data.clone(); let data = unsafe { env.create_buffer_with_borrowed_data( data.as_ptr(), diff --git a/crates/edr_napi/src/logger.rs b/crates/edr_napi/src/logger.rs index 607ffe4b65..1e0b64016b 100644 --- a/crates/edr_napi/src/logger.rs +++ b/crates/edr_napi/src/logger.rs @@ -840,9 +840,9 @@ impl LogCollector { if let Some(to) = before_message.to { // Call let is_precompile = { - let num_precompiles = - Precompiles::new(precompile::SpecId::from_spec_id(spec_id)).len(); - precompile::is_precompile(to, num_precompiles) + let precompiles = + Precompiles::new(precompile::PrecompileSpecId::from_spec_id(spec_id)); + precompiles.contains(&to) }; if is_precompile { diff --git a/crates/edr_napi/src/result.rs b/crates/edr_napi/src/result.rs index 856db97db7..0368d71e2d 100644 --- a/crates/edr_napi/src/result.rs +++ b/crates/edr_napi/src/result.rs @@ -19,17 +19,17 @@ pub enum SuccessReason { SelfDestruct, } -impl From for SuccessReason { - fn from(eval: edr_evm::Eval) -> Self { +impl From for SuccessReason { + fn from(eval: edr_evm::SuccessReason) -> Self { match eval { - edr_evm::Eval::Stop => Self::Stop, - edr_evm::Eval::Return => Self::Return, - edr_evm::Eval::SelfDestruct => Self::SelfDestruct, + edr_evm::SuccessReason::Stop => Self::Stop, + edr_evm::SuccessReason::Return => Self::Return, + edr_evm::SuccessReason::SelfDestruct => Self::SelfDestruct, } } } -impl From for edr_evm::Eval { +impl From for edr_evm::SuccessReason { fn from(value: SuccessReason) -> Self { match value { SuccessReason::Stop => Self::Stop, @@ -97,43 +97,47 @@ pub enum ExceptionalHalt { /// Error on created contract that begins with EF CreateContractStartingWithEF, /// EIP-3860: Limit and meter initcode. Initcode size limit exceeded. - CreateInitcodeSizeLimit, + CreateInitCodeSizeLimit, } -impl From for ExceptionalHalt { - fn from(halt: edr_evm::Halt) -> Self { +impl From for ExceptionalHalt { + fn from(halt: edr_evm::HaltReason) -> Self { match halt { - edr_evm::Halt::OutOfGas(..) => ExceptionalHalt::OutOfGas, - edr_evm::Halt::OpcodeNotFound => ExceptionalHalt::OpcodeNotFound, - edr_evm::Halt::InvalidFEOpcode => ExceptionalHalt::InvalidFEOpcode, - edr_evm::Halt::InvalidJump => ExceptionalHalt::InvalidJump, - edr_evm::Halt::NotActivated => ExceptionalHalt::NotActivated, - edr_evm::Halt::StackUnderflow => ExceptionalHalt::StackUnderflow, - edr_evm::Halt::StackOverflow => ExceptionalHalt::StackOverflow, - edr_evm::Halt::OutOfOffset => ExceptionalHalt::OutOfOffset, - edr_evm::Halt::CreateCollision => ExceptionalHalt::CreateCollision, - edr_evm::Halt::PrecompileError => ExceptionalHalt::PrecompileError, - edr_evm::Halt::NonceOverflow => ExceptionalHalt::NonceOverflow, - edr_evm::Halt::CreateContractSizeLimit => ExceptionalHalt::CreateContractSizeLimit, - edr_evm::Halt::CreateContractStartingWithEF => { + edr_evm::HaltReason::OutOfGas(..) => ExceptionalHalt::OutOfGas, + edr_evm::HaltReason::OpcodeNotFound => ExceptionalHalt::OpcodeNotFound, + edr_evm::HaltReason::InvalidFEOpcode => ExceptionalHalt::InvalidFEOpcode, + edr_evm::HaltReason::InvalidJump => ExceptionalHalt::InvalidJump, + edr_evm::HaltReason::NotActivated => ExceptionalHalt::NotActivated, + edr_evm::HaltReason::StackUnderflow => ExceptionalHalt::StackUnderflow, + edr_evm::HaltReason::StackOverflow => ExceptionalHalt::StackOverflow, + edr_evm::HaltReason::OutOfOffset => ExceptionalHalt::OutOfOffset, + edr_evm::HaltReason::CreateCollision => ExceptionalHalt::CreateCollision, + edr_evm::HaltReason::PrecompileError => ExceptionalHalt::PrecompileError, + edr_evm::HaltReason::NonceOverflow => ExceptionalHalt::NonceOverflow, + edr_evm::HaltReason::CreateContractSizeLimit => { + ExceptionalHalt::CreateContractSizeLimit + } + edr_evm::HaltReason::CreateContractStartingWithEF => { ExceptionalHalt::CreateContractStartingWithEF } - edr_evm::Halt::CreateInitcodeSizeLimit => ExceptionalHalt::CreateInitcodeSizeLimit, - edr_evm::Halt::OverflowPayment - | edr_evm::Halt::StateChangeDuringStaticCall - | edr_evm::Halt::CallNotAllowedInsideStatic - | edr_evm::Halt::OutOfFund - | edr_evm::Halt::CallTooDeep => { + edr_evm::HaltReason::CreateInitCodeSizeLimit => { + ExceptionalHalt::CreateInitCodeSizeLimit + } + edr_evm::HaltReason::OverflowPayment + | edr_evm::HaltReason::StateChangeDuringStaticCall + | edr_evm::HaltReason::CallNotAllowedInsideStatic + | edr_evm::HaltReason::OutOfFunds + | edr_evm::HaltReason::CallTooDeep => { unreachable!("Internal halts that can be only found inside Inspector: {halt:?}") } } } } -impl From for edr_evm::Halt { +impl From for edr_evm::HaltReason { fn from(value: ExceptionalHalt) -> Self { match value { - ExceptionalHalt::OutOfGas => Self::OutOfGas(edr_evm::OutOfGasError::BasicOutOfGas), + ExceptionalHalt::OutOfGas => Self::OutOfGas(edr_evm::OutOfGasError::Basic), ExceptionalHalt::OpcodeNotFound => Self::OpcodeNotFound, ExceptionalHalt::InvalidFEOpcode => Self::InvalidFEOpcode, ExceptionalHalt::InvalidJump => Self::InvalidJump, @@ -146,7 +150,7 @@ impl From for edr_evm::Halt { ExceptionalHalt::NonceOverflow => Self::NonceOverflow, ExceptionalHalt::CreateContractSizeLimit => Self::CreateContractSizeLimit, ExceptionalHalt::CreateContractStartingWithEF => Self::CreateContractStartingWithEF, - ExceptionalHalt::CreateInitcodeSizeLimit => Self::CreateInitcodeSizeLimit, + ExceptionalHalt::CreateInitCodeSizeLimit => Self::CreateInitCodeSizeLimit, } } } diff --git a/crates/edr_napi/src/trace.rs b/crates/edr_napi/src/trace.rs index 46251acec0..e27f353fae 100644 --- a/crates/edr_napi/src/trace.rs +++ b/crates/edr_napi/src/trace.rs @@ -1,6 +1,6 @@ use std::{mem, sync::Arc}; -use edr_evm::{trace::BeforeMessage, OPCODE_JUMPMAP}; +use edr_evm::{interpreter::OPCODE_JUMPMAP, trace::BeforeMessage}; use napi::{ bindgen_prelude::{BigInt, Buffer, Either3}, Env, JsBuffer, JsBufferValue, diff --git a/crates/edr_provider/src/data/inspector.rs b/crates/edr_provider/src/console_log.rs similarity index 65% rename from crates/edr_provider/src/data/inspector.rs rename to crates/edr_provider/src/console_log.rs index f81b58584c..e75bd4c7c4 100644 --- a/crates/edr_provider/src/data/inspector.rs +++ b/crates/edr_provider/src/console_log.rs @@ -1,97 +1,53 @@ -use core::fmt::Debug; use std::sync::Arc; -use dyn_clone::DynClone; use edr_eth::{Address, Bytes}; -use edr_evm::{CallInputs, EVMData, Gas, Inspector, InstructionResult, TransactTo}; - -use crate::data::CONSOLE_ADDRESS; - -/// The result of executing a call override. -#[derive(Debug)] -pub struct CallOverrideResult { - pub result: Bytes, - pub should_revert: bool, -} - -pub trait SyncCallOverride: - Fn(Address, Bytes) -> Option + DynClone + Send + Sync -{ -} +use edr_evm::{ + address, + db::Database, + evm::{EvmHandler, FrameOrResult}, + EVMError, GetContextData, +}; + +const CONSOLE_ADDRESS: Address = address!("000000000000000000636F6e736F6c652e6c6f67"); + +/// Registers the `ConsoleLogCollector`'s handles. +pub fn register_console_log_handles< + DatabaseT: Database, + ContextT: GetContextData, +>( + handler: &mut EvmHandler<'_, ContextT, DatabaseT>, +) { + let old_handle = handler.execution.call.clone(); + handler.execution.call = Arc::new( + move |ctx, inputs| -> Result> { + if inputs.contract == CONSOLE_ADDRESS { + let collector = ctx.external.get_context_data(); + collector.record_console_log(inputs.input.clone()); + } -impl SyncCallOverride for F where - F: Fn(Address, Bytes) -> Option + DynClone + Send + Sync -{ + old_handle(ctx, inputs) + }, + ); } -dyn_clone::clone_trait_object!(SyncCallOverride); - -pub(super) struct EvmInspector { - console_log_encoded_messages: Vec, - call_override: Option>, +#[derive(Default)] +pub struct ConsoleLogCollector { + encoded_messages: Vec, } -impl EvmInspector { - pub fn new(call_override: Option>) -> Self { - Self { - console_log_encoded_messages: Vec::new(), - call_override, - } +impl ConsoleLogCollector { + /// Returns the collected `console.log` messages. + pub fn into_encoded_messages(self) -> Vec { + self.encoded_messages } - pub fn into_console_log_encoded_messages(self) -> Vec { - self.console_log_encoded_messages - } -} - -impl Debug for EvmInspector { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("EvmInspector") - .field( - "console_log_encoded_messages", - &self.console_log_encoded_messages, - ) - .field("call_override", &"") - .finish() - } -} - -impl Inspector for EvmInspector { - fn call( - &mut self, - data: &mut EVMData<'_, DatabaseErrorT>, - inputs: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { - if inputs.contract == *CONSOLE_ADDRESS { - self.console_log_encoded_messages.push(inputs.input.clone()); - } - - if let TransactTo::Call(_) = data.env.tx.transact_to { - if let Some(call_override) = &self.call_override { - let out = (call_override)(inputs.contract, inputs.input.clone()); - if let Some(out) = out { - let instruction_result = if out.should_revert { - InstructionResult::Revert - } else { - InstructionResult::Return - }; - - return (instruction_result, Gas::new(inputs.gas_limit), out.result); - } - } - } - - ( - InstructionResult::Continue, - Gas::new(inputs.gas_limit), - Bytes::new(), - ) + fn record_console_log(&mut self, encoded_message: Bytes) { + self.encoded_messages.push(encoded_message); } } #[cfg(test)] pub(crate) mod tests { - use core::fmt::Debug; use anyhow::Context; diff --git a/crates/edr_provider/src/data.rs b/crates/edr_provider/src/data.rs index 55d821164f..13de7c1907 100644 --- a/crates/edr_provider/src/data.rs +++ b/crates/edr_provider/src/data.rs @@ -1,7 +1,6 @@ mod account; mod call; mod gas; -mod inspector; use std::{ cmp, @@ -36,39 +35,36 @@ use edr_evm::{ }, db::StateRef, debug_trace_transaction, execution_result_to_debug_result, mempool, mine_block, + register_eip_3155_tracer_handles, state::{ AccountModifierFn, IrregularState, StateDiff, StateError, StateOverride, StateOverrides, SyncState, }, - trace::{Trace, TraceCollector}, + trace::Trace, Account, AccountInfo, BlobExcessGasAndPrice, Block, BlockEnv, Bytecode, CfgEnv, - DebugTraceConfig, DebugTraceResult, DualInspector, ExecutableTransaction, ExecutionResult, - HashMap, HashSet, MemPool, OrderedTransaction, RandomHashGenerator, StorageSlot, SyncBlock, - TracerEip3155, TxEnv, KECCAK_EMPTY, + CfgEnvWithHandlerCfg, DebugContext, DebugTraceConfig, DebugTraceResult, ExecutableTransaction, + ExecutionResult, HashMap, HashSet, MemPool, OrderedTransaction, RandomHashGenerator, + StorageSlot, SyncBlock, TracerEip3155, TxEnv, KECCAK_EMPTY, }; use ethers_core::types::transaction::eip712::{Eip712, TypedData}; use gas::gas_used_ratio; use indexmap::IndexMap; use itertools::izip; -use lazy_static::lazy_static; use lru::LruCache; use tokio::runtime; -use self::{ - account::{create_accounts, InitialAccounts}, - gas::{BinarySearchEstimationResult, CheckGasResult}, - inspector::EvmInspector, -}; -pub use crate::data::inspector::{CallOverrideResult, SyncCallOverride}; +use self::account::{create_accounts, InitialAccounts}; use crate::{ data::{ call::{run_call, RunCallArgs}, gas::{compute_rewards, BinarySearchEstimationArgs, CheckGasLimitArgs}, }, debug_mine::{DebugMineBlockResult, DebugMineBlockResultAndState}, + debugger::{register_debugger_handles, Debugger}, error::{EstimateGasFailure, TransactionFailure, TransactionFailureWithTraces}, filter::{bloom_contains_log_filter, filter_logs, Filter, FilterData, LogFilter}, logger::SyncLogger, + mock::{Mocker, SyncCallOverride}, pending::BlockchainWithPending, requests::hardhat::rpc_types::{ForkConfig, ForkMetadata}, snapshot::Snapshot, @@ -561,7 +557,7 @@ impl ProviderData { gas_limit: U256::from(header.gas_limit), basefee: header.base_fee_per_gas.unwrap_or_default(), difficulty: U256::from(header.difficulty), - prevrandao: if cfg_env.spec_id >= SpecId::MERGE { + prevrandao: if cfg_env.handler_cfg.spec_id >= SpecId::MERGE { Some(header.mix_hash) } else { None @@ -606,7 +602,10 @@ impl ProviderData { state_overrides: &StateOverrides::default(), cfg_env: cfg_env.clone(), tx_env: tx_env.clone(), - inspector: Some(&mut tracer), + debug_context: Some(DebugContext { + data: &mut tracer, + register_handles_fn: register_eip_3155_tracer_handles, + }), })?; Ok(execution_result_to_debug_result(result, tracer)) @@ -627,10 +626,7 @@ impl ProviderData { let state_overrides = StateOverrides::default(); - let mut inspector = DualInspector::new( - TraceCollector::default(), - EvmInspector::new(self.call_override.clone()), - ); + let mut debugger = Debugger::with_mocker(Mocker::new(self.call_override.clone())); self.execute_in_block_context(Some(block_spec), |blockchain, block, state| { let header = block.header(); @@ -645,23 +641,41 @@ impl ProviderData { state_overrides: &state_overrides, cfg_env: cfg_env.clone(), tx_env: tx_env.clone(), - inspector: Some(&mut inspector), + debug_context: Some(DebugContext { + data: &mut debugger, + register_handles_fn: register_debugger_handles, + }), })?; - let (tracer, inspector) = inspector.into_parts(); - let trace = tracer.into_trace(); + let Debugger { + console_logger, + mut trace_collector, + .. + } = debugger; let mut initial_estimation = match result { ExecutionResult::Success { gas_used, .. } => Ok(gas_used), - ExecutionResult::Revert { output, .. } => { - Err(TransactionFailure::revert(output, None, trace.clone())) - } - ExecutionResult::Halt { reason, .. } => { - Err(TransactionFailure::halt(reason, None, trace.clone())) - } + ExecutionResult::Revert { output, .. } => Err(TransactionFailure::revert( + output, + None, + trace_collector + .traces() + .first() + .expect("Must have a trace") + .clone(), + )), + ExecutionResult::Halt { reason, .. } => Err(TransactionFailure::halt( + reason, + None, + trace_collector + .traces() + .first() + .expect("Must have a trace") + .clone(), + )), } .map_err(|failure| EstimateGasFailure { - console_log_inputs: inspector.into_console_log_encoded_messages(), + console_log_inputs: console_logger.into_encoded_messages(), transaction_failure: TransactionFailureWithTraces { traces: vec![failure.solidity_trace.clone()], failure, @@ -673,10 +687,8 @@ impl ProviderData { initial_estimation = minimum_cost + 1; } - let mut traces = vec![trace]; - // Test if the transaction would be successful with the initial estimation - let CheckGasResult { success, trace } = gas::check_gas_limit(CheckGasLimitArgs { + let success = gas::check_gas_limit(CheckGasLimitArgs { blockchain, header, state, @@ -684,25 +696,21 @@ impl ProviderData { cfg_env: cfg_env.clone(), tx_env: tx_env.clone(), gas_limit: initial_estimation, + trace_collector: &mut trace_collector, })?; - traces.push(trace); - // Return the initial estimation if it was successful if success { return Ok(EstimateGasResult { estimation: initial_estimation, - traces, + traces: trace_collector.into_traces(), }); } // Correct the initial estimation if the transaction failed with the actually // used gas limit. This can happen if the execution logic is based // on the available gas. - let BinarySearchEstimationResult { - estimation, - traces: mut estimation_traces, - } = gas::binary_search_estimation(BinarySearchEstimationArgs { + let estimation = gas::binary_search_estimation(BinarySearchEstimationArgs { blockchain, header, state, @@ -711,10 +719,10 @@ impl ProviderData { tx_env: tx_env.clone(), lower_bound: initial_estimation, upper_bound: header.gas_limit, + trace_collector: &mut trace_collector, })?; - traces.append(&mut estimation_traces); - + let traces = trace_collector.into_traces(); Ok(EstimateGasResult { estimation, traces }) })? } @@ -1345,10 +1353,7 @@ impl ProviderData { let cfg_env = self.create_evm_config(block_spec)?; let tx_env = transaction.into(); - let mut inspector = DualInspector::new( - TraceCollector::default(), - EvmInspector::new(self.call_override.clone()), - ); + let mut debugger = Debugger::with_mocker(Mocker::new(self.call_override.clone())); self.execute_in_block_context(block_spec, |blockchain, block, state| { let execution_result = call::run_call(RunCallArgs { @@ -1358,15 +1363,26 @@ impl ProviderData { state_overrides, cfg_env, tx_env, - inspector: Some(&mut inspector), + debug_context: Some(DebugContext { + data: &mut debugger, + register_handles_fn: register_debugger_handles, + }), })?; - let (tracer, inspector) = inspector.into_parts(); + let Debugger { + console_logger, + trace_collector, + .. + } = debugger; + + let mut traces = trace_collector.into_traces(); + // Should only have a single raw trace + assert_eq!(traces.len(), 1); Ok(CallResult { - console_log_inputs: inspector.into_console_log_encoded_messages(), + console_log_inputs: console_logger.into_encoded_messages(), execution_result, - trace: tracer.into_trace(), + trace: traces.pop().expect("Must have a trace"), }) })? } @@ -1807,7 +1823,7 @@ impl ProviderData { fn create_evm_config( &self, block_spec: Option<&BlockSpec>, - ) -> Result> { + ) -> Result> { let block_number = block_spec .map(|block_spec| self.block_number_by_block_spec(block_spec)) .transpose()? @@ -1819,17 +1835,16 @@ impl ProviderData { self.blockchain.spec_id() }; - let mut evm_config = CfgEnv::default(); - evm_config.chain_id = self.blockchain.chain_id(); - evm_config.spec_id = spec_id; - evm_config.limit_contract_code_size = if self.allow_unlimited_contract_size { + let mut cfg_env = CfgEnv::default(); + cfg_env.chain_id = self.blockchain.chain_id(); + cfg_env.limit_contract_code_size = if self.allow_unlimited_contract_size { Some(usize::MAX) } else { None }; - evm_config.disable_eip3607 = true; + cfg_env.disable_eip3607 = true; - Ok(evm_config) + Ok(CfgEnvWithHandlerCfg::new_with_spec_id(cfg_env, spec_id)) } fn execute_in_block_context( @@ -1885,32 +1900,44 @@ impl ProviderData { let evm_config = self.create_evm_config(None)?; - if evm_config.spec_id >= SpecId::CANCUN { + if evm_config.handler_cfg.spec_id >= SpecId::CANCUN { options.parent_beacon_block_root = options .parent_beacon_block_root .or_else(|| Some(self.parent_beacon_block_root_generator.next_value())); } - let mut inspector = EvmInspector::new(self.call_override.clone()); + let mut debugger = Debugger::with_mocker(Mocker::new(self.call_override.clone())); let state_to_be_modified = (*self.current_state()?).clone(); let result = mine_block( - &*self.blockchain, + self.blockchain.as_ref(), state_to_be_modified, &self.mem_pool, &evm_config, options, self.min_gas_price, self.initial_config.mining.mem_pool.order, - miner_reward(evm_config.spec_id).unwrap_or(U256::ZERO), + miner_reward(evm_config.handler_cfg.spec_id).unwrap_or(U256::ZERO), self.dao_activation_block, - Some(&mut inspector), + Some(DebugContext { + data: &mut debugger, + register_handles_fn: register_debugger_handles, + }), )?; + let Debugger { + console_logger, + trace_collector, + .. + } = debugger; + + let traces = trace_collector.into_traces(); + Ok(DebugMineBlockResultAndState::new( result, - inspector.into_console_log_encoded_messages(), + traces, + console_logger.into_encoded_messages(), )) } @@ -2384,12 +2411,6 @@ pub struct BlockDataForTransaction { pub transaction_index: u64, } -lazy_static! { - static ref CONSOLE_ADDRESS: Address = "0x000000000000000000636F6e736F6c652e6c6f67" - .parse() - .expect("static ok"); -} - #[cfg(test)] pub(crate) mod test_utils { use std::convert::Infallible; @@ -2543,7 +2564,7 @@ mod tests { use super::{test_utils::ProviderTestFixture, *}; use crate::{ - data::inspector::tests::{deploy_console_log_contract, ConsoleLogTransaction}, + console_log::tests::{deploy_console_log_contract, ConsoleLogTransaction}, requests::eth::resolve_call_request, test_utils::{ create_test_config, create_test_config_with_fork, one_ether, FORK_BLOCK_NUMBER, diff --git a/crates/edr_provider/src/data/call.rs b/crates/edr_provider/src/data/call.rs index fe39821368..8a1974c28e 100644 --- a/crates/edr_provider/src/data/call.rs +++ b/crates/edr_provider/src/data/call.rs @@ -7,26 +7,39 @@ use edr_eth::{ use edr_evm::{ blockchain::{BlockchainError, SyncBlockchain}, guaranteed_dry_run, - state::{StateError, StateOverrides, SyncState}, - BlobExcessGasAndPrice, BlockEnv, CfgEnv, ExecutionResult, SyncInspector, TxEnv, + state::{StateError, StateOverrides, StateRefOverrider, SyncState}, + BlobExcessGasAndPrice, BlockEnv, CfgEnvWithHandlerCfg, DebugContext, ExecutionResult, TxEnv, }; use crate::ProviderError; -pub(super) struct RunCallArgs<'a> { +pub(super) struct RunCallArgs<'a, 'evm, DebugDataT> +where + 'a: 'evm, +{ pub blockchain: &'a dyn SyncBlockchain, pub header: &'a Header, pub state: &'a dyn SyncState, pub state_overrides: &'a StateOverrides, - pub cfg_env: CfgEnv, + pub cfg_env: CfgEnvWithHandlerCfg, pub tx_env: TxEnv, - pub inspector: Option<&'a mut dyn SyncInspector>, + pub debug_context: Option< + DebugContext< + 'evm, + BlockchainError, + DebugDataT, + StateRefOverrider<'a, &'evm dyn SyncState>, + >, + >, } /// Execute a transaction as a call. Returns the gas used and the output. -pub(super) fn run_call( - args: RunCallArgs<'_>, -) -> Result> { +pub(super) fn run_call<'a, 'evm, DebugDataT, LoggerErrorT: Debug>( + args: RunCallArgs<'a, 'evm, DebugDataT>, +) -> Result> +where + 'a: 'evm, +{ let RunCallArgs { blockchain, header, @@ -34,7 +47,7 @@ pub(super) fn run_call( state_overrides, cfg_env, tx_env, - inspector, + debug_context, } = args; let block = BlockEnv { @@ -44,7 +57,7 @@ pub(super) fn run_call( gas_limit: U256::from(header.gas_limit), basefee: U256::ZERO, difficulty: header.difficulty, - prevrandao: if cfg_env.spec_id >= SpecId::MERGE { + prevrandao: if cfg_env.handler_cfg.spec_id >= SpecId::MERGE { Some(header.mix_hash) } else { None @@ -62,7 +75,7 @@ pub(super) fn run_call( cfg_env, tx_env, block, - inspector, + debug_context, ) .map_or_else( |error| Err(ProviderError::RunTransaction(error)), diff --git a/crates/edr_provider/src/data/gas.rs b/crates/edr_provider/src/data/gas.rs index 4d9d3d9299..7f6c565b57 100644 --- a/crates/edr_provider/src/data/gas.rs +++ b/crates/edr_provider/src/data/gas.rs @@ -5,8 +5,8 @@ use edr_eth::{block::Header, reward_percentile::RewardPercentile, U256}; use edr_evm::{ blockchain::{BlockchainError, SyncBlockchain}, state::{StateError, StateOverrides, SyncState}, - trace::{Trace, TraceCollector}, - CfgEnv, ExecutionResult, SyncBlock, TxEnv, + trace::{register_trace_collector_handles, TraceCollector}, + CfgEnvWithHandlerCfg, DebugContext, ExecutionResult, SyncBlock, TxEnv, }; use itertools::Itertools; @@ -20,14 +20,10 @@ pub(super) struct CheckGasLimitArgs<'a> { pub header: &'a Header, pub state: &'a dyn SyncState, pub state_overrides: &'a StateOverrides, - pub cfg_env: CfgEnv, + pub cfg_env: CfgEnvWithHandlerCfg, pub tx_env: TxEnv, pub gas_limit: u64, -} - -pub(super) struct CheckGasResult { - pub success: bool, - pub trace: Trace, + pub trace_collector: &'a mut TraceCollector, } /// Test if the transaction successfully executes with the given gas limit. @@ -35,7 +31,7 @@ pub(super) struct CheckGasResult { /// or funds or reverts. Returns an error for any other halt reason. pub(super) fn check_gas_limit( args: CheckGasLimitArgs<'_>, -) -> Result> { +) -> Result> { let CheckGasLimitArgs { blockchain, header, @@ -44,12 +40,11 @@ pub(super) fn check_gas_limit( cfg_env, mut tx_env, gas_limit, + trace_collector, } = args; tx_env.gas_limit = gas_limit; - let mut tracer = TraceCollector::default(); - let result = call::run_call(RunCallArgs { blockchain, header, @@ -57,14 +52,13 @@ pub(super) fn check_gas_limit( state_overrides, cfg_env, tx_env, - inspector: Some(&mut tracer), + debug_context: Some(DebugContext { + data: trace_collector, + register_handles_fn: register_trace_collector_handles, + }), })?; - let success = matches!(result, ExecutionResult::Success { .. }); - Ok(CheckGasResult { - success, - trace: tracer.into_trace(), - }) + Ok(matches!(result, ExecutionResult::Success { .. })) } pub(super) struct BinarySearchEstimationArgs<'a> { @@ -72,15 +66,11 @@ pub(super) struct BinarySearchEstimationArgs<'a> { pub header: &'a Header, pub state: &'a dyn SyncState, pub state_overrides: &'a StateOverrides, - pub cfg_env: CfgEnv, + pub cfg_env: CfgEnvWithHandlerCfg, pub tx_env: TxEnv, pub lower_bound: u64, pub upper_bound: u64, -} - -pub(super) struct BinarySearchEstimationResult { - pub estimation: u64, - pub traces: Vec, + pub trace_collector: &'a mut TraceCollector, } /// Search for a tight upper bound on the gas limit that will allow the @@ -88,7 +78,7 @@ pub(super) struct BinarySearchEstimationResult { /// recursive. pub(super) fn binary_search_estimation( args: BinarySearchEstimationArgs<'_>, -) -> Result> { +) -> Result> { const MAX_ITERATIONS: usize = 20; let BinarySearchEstimationArgs { @@ -100,11 +90,11 @@ pub(super) fn binary_search_estimation( tx_env, mut lower_bound, mut upper_bound, + trace_collector, } = args; let mut i = 0; - let mut traces = Vec::new(); while upper_bound - lower_bound > min_difference(lower_bound) && i < MAX_ITERATIONS { let mut mid = lower_bound + (upper_bound - lower_bound) / 2; if i == 0 { @@ -114,7 +104,7 @@ pub(super) fn binary_search_estimation( mid = cmp::min(mid, initial_mid); } - let CheckGasResult { success, trace } = check_gas_limit(CheckGasLimitArgs { + let success = check_gas_limit(CheckGasLimitArgs { blockchain, header, state, @@ -122,8 +112,8 @@ pub(super) fn binary_search_estimation( cfg_env: cfg_env.clone(), tx_env: tx_env.clone(), gas_limit: mid, + trace_collector, })?; - traces.push(trace); if success { upper_bound = mid; @@ -134,10 +124,7 @@ pub(super) fn binary_search_estimation( i += 1; } - Ok(BinarySearchEstimationResult { - estimation: upper_bound, - traces, - }) + Ok(upper_bound) } // Matches Hardhat diff --git a/crates/edr_provider/src/debug_mine.rs b/crates/edr_provider/src/debug_mine.rs index 208d3e0d3b..1305af8e11 100644 --- a/crates/edr_provider/src/debug_mine.rs +++ b/crates/edr_provider/src/debug_mine.rs @@ -26,10 +26,11 @@ pub struct DebugMineBlockResultAndState { } impl DebugMineBlockResultAndState { - /// Constructs a new instance from a [`MineBlockResultAndState`] and decoded - /// console log messages. + /// Constructs a new instance from a [`MineBlockResultAndState`], + /// transaction traces, and decoded console log messages. pub fn new( result: MineBlockResultAndState, + transaction_traces: Vec, console_log_decoded_messages: Vec, ) -> Self { Self { @@ -37,7 +38,7 @@ impl DebugMineBlockResultAndState { state: result.state, state_diff: result.state_diff, transaction_results: result.transaction_results, - transaction_traces: result.transaction_traces, + transaction_traces, console_log_inputs: console_log_decoded_messages, } } diff --git a/crates/edr_provider/src/debugger.rs b/crates/edr_provider/src/debugger.rs new file mode 100644 index 0000000000..b0201f7c0a --- /dev/null +++ b/crates/edr_provider/src/debugger.rs @@ -0,0 +1,63 @@ +use core::fmt::Debug; + +use edr_evm::{ + db::Database, + evm::EvmHandler, + trace::{register_trace_collector_handles, TraceCollector}, + GetContextData, +}; + +use crate::{ + console_log::{register_console_log_handles, ConsoleLogCollector}, + mock::{register_mocking_handles, Mocker}, +}; + +/// Registers the EIP-3155 tracer handles. +pub fn register_debugger_handles( + handler: &mut EvmHandler<'_, ContextT, DatabaseT>, +) where + DatabaseT: Database, + DatabaseT::Error: Debug, + ContextT: GetContextData + + GetContextData + + GetContextData, +{ + register_console_log_handles(handler); + register_mocking_handles(handler); + register_trace_collector_handles(handler); +} + +pub struct Debugger { + pub console_logger: ConsoleLogCollector, + pub mocker: Mocker, + pub trace_collector: TraceCollector, +} + +impl Debugger { + /// Creates a new instance with the provided mocker. + pub fn with_mocker(mocker: Mocker) -> Self { + Self { + console_logger: ConsoleLogCollector::default(), + mocker, + trace_collector: TraceCollector::default(), + } + } +} + +impl GetContextData for Debugger { + fn get_context_data(&mut self) -> &mut ConsoleLogCollector { + &mut self.console_logger + } +} + +impl GetContextData for Debugger { + fn get_context_data(&mut self) -> &mut Mocker { + &mut self.mocker + } +} + +impl GetContextData for Debugger { + fn get_context_data(&mut self) -> &mut TraceCollector { + &mut self.trace_collector + } +} diff --git a/crates/edr_provider/src/error.rs b/crates/edr_provider/src/error.rs index 2cb90c133e..776fa0995d 100644 --- a/crates/edr_provider/src/error.rs +++ b/crates/edr_provider/src/error.rs @@ -11,7 +11,7 @@ use edr_evm::{ hex, state::{AccountOverrideConversionError, StateError}, trace::Trace, - DebugTraceError, ExecutionResult, Halt, MemPoolAddTransactionError, MineBlockError, + DebugTraceError, ExecutionResult, HaltReason, MemPoolAddTransactionError, MineBlockError, OutOfGasError, TransactionCreationError, TransactionError, }; use ethers_core::types::transaction::eip712::Eip712Error; @@ -264,7 +264,7 @@ impl From> for jsonrpc::Error { ProviderError::TransactionFailed(inner) if matches!( inner.failure.reason, - TransactionFailureReason::Inner(Halt::CreateContractSizeLimit) + TransactionFailureReason::Inner(HaltReason::CreateContractSizeLimit) ) => { "Transaction reverted: trying to deploy a contract whose code is too large".into() @@ -348,12 +348,12 @@ impl TransactionFailure { } } - pub fn halt(halt: Halt, tx_hash: Option, solidity_trace: Trace) -> Self { + pub fn halt(halt: HaltReason, tx_hash: Option, solidity_trace: Trace) -> Self { let reason = match halt { - Halt::OpcodeNotFound | Halt::InvalidFEOpcode => { + HaltReason::OpcodeNotFound | HaltReason::InvalidFEOpcode => { TransactionFailureReason::OpcodeNotFound } - Halt::OutOfGas(error) => TransactionFailureReason::OutOfGas(error), + HaltReason::OutOfGas(error) => TransactionFailureReason::OutOfGas(error), halt => TransactionFailureReason::Inner(halt), }; @@ -384,7 +384,7 @@ impl std::fmt::Display for TransactionFailure { #[derive(Clone, Debug, serde::Serialize)] pub enum TransactionFailureReason { - Inner(Halt), + Inner(HaltReason), OpcodeNotFound, OutOfGas(OutOfGasError), Revert(Bytes), diff --git a/crates/edr_provider/src/filter/criteria.rs b/crates/edr_provider/src/filter/criteria.rs index b5428422e7..51f9d58cf4 100644 --- a/crates/edr_provider/src/filter/criteria.rs +++ b/crates/edr_provider/src/filter/criteria.rs @@ -42,7 +42,7 @@ pub fn filter_logs<'i>( .to_block .map_or(true, |to_block| log.block_number <= to_block) && matches_address_filter(&log.address, &filter.addresses) - && matches_topics_filter(&log.topics, &filter.normalized_topics) + && matches_topics_filter(log.topics(), &filter.normalized_topics) }) .map(LogOutput::from) .collect() diff --git a/crates/edr_provider/src/lib.rs b/crates/edr_provider/src/lib.rs index 87d832a5f7..fb70c72b4d 100644 --- a/crates/edr_provider/src/lib.rs +++ b/crates/edr_provider/src/lib.rs @@ -1,10 +1,13 @@ mod config; +mod console_log; mod data; mod debug_mine; +mod debugger; mod error; mod filter; mod interval; mod logger; +mod mock; mod pending; mod requests; mod snapshot; @@ -16,20 +19,21 @@ pub mod test_utils; use core::fmt::Debug; use std::sync::Arc; -use data::SyncCallOverride; use edr_evm::{blockchain::BlockchainError, trace::Trace, HashSet}; use lazy_static::lazy_static; use logger::SyncLogger; +use mock::SyncCallOverride; use parking_lot::Mutex; use requests::{eth::handle_set_interval_mining, hardhat::rpc_types::ResetProviderConfig}; use tokio::{runtime, sync::Mutex as AsyncMutex, task}; pub use self::{ config::*, - data::{CallOverrideResult, CallResult}, + data::CallResult, debug_mine::DebugMineBlockResult, error::{EstimateGasFailure, ProviderError, TransactionFailure, TransactionFailureReason}, logger::{Logger, NoopLogger}, + mock::CallOverrideResult, requests::{ hardhat::rpc_types as hardhat_rpc_types, InvalidRequestReason, MethodInvocation, OneUsizeOrTwo, ProviderRequest, U64OrUsize, @@ -39,9 +43,8 @@ pub use self::{ use self::{ data::{CreationError, ProviderData}, interval::IntervalMiner, - requests::{eth, hardhat}, + requests::{debug, eth, hardhat}, }; -use crate::requests::debug; lazy_static! { pub static ref PRIVATE_RPC_METHODS: HashSet<&'static str> = { diff --git a/crates/edr_provider/src/mock.rs b/crates/edr_provider/src/mock.rs new file mode 100644 index 0000000000..05c1c396ff --- /dev/null +++ b/crates/edr_provider/src/mock.rs @@ -0,0 +1,78 @@ +use std::sync::Arc; + +use dyn_clone::DynClone; +use edr_eth::{Address, Bytes}; +use edr_evm::{ + db::Database, + evm::{EvmHandler, FrameOrResult, FrameResult}, + interpreter::{CallOutcome, Gas, InstructionResult, InterpreterResult}, + EVMError, GetContextData, +}; + +/// The result of executing a call override. +#[derive(Debug)] +pub struct CallOverrideResult { + pub output: Bytes, + pub should_revert: bool, +} + +pub trait SyncCallOverride: + Fn(Address, Bytes) -> Option + DynClone + Send + Sync +{ +} + +impl SyncCallOverride for F where + F: Fn(Address, Bytes) -> Option + DynClone + Send + Sync +{ +} + +dyn_clone::clone_trait_object!(SyncCallOverride); + +/// Registers the `Mocker`'s handles. +pub fn register_mocking_handles>( + handler: &mut EvmHandler<'_, ContextT, DatabaseT>, +) { + let old_handle = handler.execution.call.clone(); + handler.execution.call = Arc::new( + move |ctx, inputs| -> Result> { + let mocker = ctx.external.get_context_data(); + if let Some(CallOverrideResult { + output, + should_revert, + }) = mocker.override_call(inputs.contract, inputs.input.clone()) + { + let result = if should_revert { + InstructionResult::Revert + } else { + InstructionResult::Return + }; + + Ok(FrameOrResult::Result(FrameResult::Call(CallOutcome::new( + InterpreterResult { + result, + output, + gas: Gas::new(inputs.gas_limit), + }, + inputs.return_memory_offset, + )))) + } else { + old_handle(ctx, inputs) + } + }, + ); +} + +pub struct Mocker { + call_override: Option>, +} + +impl Mocker { + /// Constructs a new instance with the provided call override. + pub fn new(call_override: Option>) -> Self { + Self { call_override } + } + + fn override_call(&self, contract: Address, input: Bytes) -> Option { + self.call_override.as_ref().and_then(|f| f(contract, input)) + } +} diff --git a/crates/edr_provider/src/test_utils.rs b/crates/edr_provider/src/test_utils.rs index 02ac3df640..a3b1b6aa5a 100644 --- a/crates/edr_provider/src/test_utils.rs +++ b/crates/edr_provider/src/test_utils.rs @@ -14,7 +14,8 @@ use edr_evm::{ alloy_primitives::U160, blockchain::{Blockchain, ForkedBlockchain}, state::IrregularState, - Block, BlockBuilder, CfgEnv, RandomHashGenerator, RemoteBlock, + Block, BlockBuilder, CfgEnv, CfgEnvWithHandlerCfg, DebugContext, ExecutionResultWithContext, + RandomHashGenerator, RemoteBlock, }; use super::*; @@ -139,9 +140,10 @@ pub async fn run_full_block(url: String, block_number: u64, chain_id: u64) -> an let mut cfg = CfgEnv::default(); cfg.chain_id = chain_id; - cfg.spec_id = spec_id; cfg.disable_eip3607 = true; + let cfg = CfgEnvWithHandlerCfg::new_with_spec_id(cfg, spec_id); + let parent = blockchain.last_block()?; let replay_header = replay_block.header(); @@ -167,7 +169,13 @@ pub async fn run_full_block(url: String, block_number: u64, chain_id: u64) -> an blockchain.state_at_block_number(block_number - 1, irregular_state.state_overrides())?; for transaction in replay_block.transactions() { - builder.add_transaction(&blockchain, &mut state, transaction.clone(), None)?; + let debug_context: Option> = None; + let ExecutionResultWithContext { + result, + evm_context: _, + } = builder.add_transaction(&blockchain, &mut state, transaction.clone(), debug_context); + + result?; } let rewards = vec![( diff --git a/crates/edr_solidity/Cargo.toml b/crates/edr_solidity/Cargo.toml index d014914bb7..73f7c5a50e 100644 --- a/crates/edr_solidity/Cargo.toml +++ b/crates/edr_solidity/Cargo.toml @@ -5,4 +5,4 @@ edition = "2021" [dependencies] edr_eth = { version = "0.2.0-dev", path = "../edr_eth" } -revm = { git = "https://github.com/Wodann/revm", rev = "b25b44a", version = "3.5", default-features = false, features = ["dev", "serde", "std"] } +edr_evm = { version = "0.2.0-dev", path = "../edr_evm" } diff --git a/crates/edr_solidity/src/contracts_identifier.rs b/crates/edr_solidity/src/contracts_identifier.rs index f951b8f235..c332faec30 100644 --- a/crates/edr_solidity/src/contracts_identifier.rs +++ b/crates/edr_solidity/src/contracts_identifier.rs @@ -3,8 +3,8 @@ mod radix_tree; use std::collections::HashMap; use edr_eth::Bytes; +use edr_evm::interpreter::opcode; use radix_tree::RadixTree; -use revm::interpreter::opcode; use self::radix_tree::RadixNode; use crate::opcodes::opcode_length; diff --git a/crates/edr_solidity/src/opcodes.rs b/crates/edr_solidity/src/opcodes.rs index 265bcd8516..a8b66d51b9 100644 --- a/crates/edr_solidity/src/opcodes.rs +++ b/crates/edr_solidity/src/opcodes.rs @@ -1,4 +1,4 @@ -use revm::interpreter::opcode; +use edr_evm::interpreter::opcode; fn is_push(opcode: u8) -> bool { (opcode::PUSH1..=opcode::PUSH32).contains(&opcode)