From 6b288fd3d5ef9c2489cdb713bbb0c94ff64adf2a Mon Sep 17 00:00:00 2001 From: Agost Biro <5764438+agostbiro@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:07:51 +0200 Subject: [PATCH] chore: bump alloy and add `foundry-fork-db` (#712) Co-authored-by: Agost Biro Co-authored-by: Arsenii Kulikov --- Cargo.lock | 316 +++--- Cargo.toml | 62 +- crates/edr_solidity_tests/Cargo.toml | 2 - crates/foundry/cheatcodes/Cargo.toml | 2 +- crates/foundry/cheatcodes/src/error.rs | 13 +- crates/foundry/cheatcodes/src/utils.rs | 12 +- crates/foundry/evm/core/Cargo.toml | 5 +- .../evm/core/src/abi/fmt/transactions.rs | 12 +- crates/foundry/evm/core/src/abi/fmt/ui.rs | 23 +- crates/foundry/evm/core/src/backend/cow.rs | 7 +- crates/foundry/evm/core/src/backend/error.rs | 95 +- .../evm/core/src/backend/in_memory_db.rs | 3 +- crates/foundry/evm/core/src/backend/mod.rs | 115 ++- crates/foundry/evm/core/src/fork/backend.rs | 899 ------------------ crates/foundry/evm/core/src/fork/cache.rs | 660 ------------- crates/foundry/evm/core/src/fork/database.rs | 6 +- crates/foundry/evm/core/src/fork/mod.rs | 8 +- crates/foundry/evm/core/src/fork/multi.rs | 10 +- .../foundry/evm/core/src/fork/provider/mod.rs | 10 +- .../src/fork/provider/runtime_transport.rs | 2 +- crates/foundry/evm/evm/src/executors/mod.rs | 16 +- 21 files changed, 335 insertions(+), 1943 deletions(-) delete mode 100644 crates/foundry/evm/core/src/fork/backend.rs delete mode 100644 crates/foundry/evm/core/src/fork/cache.rs diff --git a/Cargo.lock b/Cargo.lock index 2b99f80651..887f5bcc3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,19 +66,6 @@ dependencies = [ "strum", ] -[[package]] -name = "alloy-consensus" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" -dependencies = [ - "alloy-eips 0.1.0", - "alloy-primitives 0.7.7", - "alloy-rlp", - "alloy-serde 0.1.0", - "c-kzg", - "serde", -] - [[package]] name = "alloy-consensus" version = "0.1.4" @@ -114,20 +101,6 @@ dependencies = [ "winnow 0.6.18", ] -[[package]] -name = "alloy-eips" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" -dependencies = [ - "alloy-primitives 0.7.7", - "alloy-rlp", - "alloy-serde 0.1.0", - "c-kzg", - "once_cell", - "serde", - "sha2", -] - [[package]] name = "alloy-eips" version = "0.1.4" @@ -160,13 +133,13 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca15afde1b6d15e3fc1c97421262b1bbb37aee45752e3c8b6d6f13f776554ff" dependencies = [ "alloy-primitives 0.7.7", - "alloy-serde 0.1.0", + "alloy-serde 0.1.4", "serde", - "serde_json", ] [[package]] @@ -183,8 +156,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d6f34930b7e3e2744bcc79056c217f00cb2abb33bc5d4ff88da7623c5bb078b" dependencies = [ "alloy-primitives 0.7.7", "serde", @@ -195,17 +169,20 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f6895fc31b48fa12306ef9b4f78b7764f8bd6d7d91cdb0a40e233704a0f23f" dependencies = [ - "alloy-consensus 0.1.0", - "alloy-eips 0.1.0", + "alloy-consensus", + "alloy-eips 0.1.4", "alloy-json-rpc", "alloy-primitives 0.7.7", - "alloy-rpc-types 0.1.0", + "alloy-rpc-types-eth", + "alloy-serde 0.1.4", "alloy-signer", "alloy-sol-types 0.7.7", "async-trait", + "auto_impl", "futures-utils-wasm", "thiserror", ] @@ -260,16 +237,18 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c538bfa893d07e27cb4f3c1ab5f451592b7c526d511d62b576a2ce59e146e4a" dependencies = [ - "alloy-eips 0.1.0", + "alloy-chains", + "alloy-consensus", + "alloy-eips 0.1.4", "alloy-json-rpc", "alloy-network", "alloy-primitives 0.7.7", "alloy-rpc-client", - "alloy-rpc-types 0.1.0", - "alloy-rpc-types-trace 0.1.0", + "alloy-rpc-types-eth", "alloy-transport", "async-stream", "async-trait", @@ -279,6 +258,7 @@ dependencies = [ "futures-utils-wasm", "lru", "pin-project", + "serde", "serde_json", "tokio", "tracing", @@ -286,8 +266,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a7341322d9bc0e49f6e9fd9f2eb8e30f73806f2dd12cbb3d6bab2694c921f87" dependencies = [ "alloy-json-rpc", "alloy-primitives 0.7.7", @@ -326,8 +307,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ba31bae67773fd5a60020bea900231f8396202b7feca4d0c70c6b59308ab4a8" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -342,46 +324,30 @@ dependencies = [ "tracing", ] -[[package]] -name = "alloy-rpc-types" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" -dependencies = [ - "alloy-consensus 0.1.0", - "alloy-eips 0.1.0", - "alloy-genesis", - "alloy-primitives 0.7.7", - "alloy-rlp", - "alloy-serde 0.1.0", - "alloy-sol-types 0.7.7", - "itertools 0.12.1", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "alloy-rpc-types" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "184a7a42c7ba9141cc9e76368356168c282c3bc3d9e5d78f3556bdfe39343447" dependencies = [ + "alloy-rpc-types-engine", "alloy-rpc-types-eth", - "alloy-rpc-types-trace 0.1.4", + "alloy-rpc-types-trace", "alloy-serde 0.1.4", ] [[package]] name = "alloy-rpc-types-engine" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e765962e3b82fd6f276a0873b5bd897e5d75a25f78fa9a6a21bd350d8e98a4e" dependencies = [ - "alloy-consensus 0.1.0", - "alloy-eips 0.1.0", + "alloy-consensus", + "alloy-eips 0.1.4", "alloy-primitives 0.7.7", "alloy-rlp", - "alloy-rpc-types 0.1.0", - "alloy-serde 0.1.0", + "alloy-rpc-types-eth", + "alloy-serde 0.1.4", "jsonwebtoken", "rand", "serde", @@ -394,7 +360,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab4123ee21f99ba4bd31bfa36ba89112a18a500f8b452f02b35708b1b951e2b9" dependencies = [ - "alloy-consensus 0.1.4", + "alloy-consensus", "alloy-eips 0.1.4", "alloy-primitives 0.7.7", "alloy-rlp", @@ -406,18 +372,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "alloy-rpc-types-trace" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" -dependencies = [ - "alloy-primitives 0.7.7", - "alloy-rpc-types 0.1.0", - "alloy-serde 0.1.0", - "serde", - "serde_json", -] - [[package]] name = "alloy-rpc-types-trace" version = "0.1.4" @@ -432,16 +386,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "alloy-serde" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" -dependencies = [ - "alloy-primitives 0.7.7", - "serde", - "serde_json", -] - [[package]] name = "alloy-serde" version = "0.1.4" @@ -466,8 +410,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b33753c09fa1ad85e5b092b8dc2372f1e337a42e84b9b4cff9fede75ba4adb32" dependencies = [ "alloy-primitives 0.7.7", "async-trait", @@ -478,11 +423,12 @@ dependencies = [ ] [[package]] -name = "alloy-signer-wallet" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" +name = "alloy-signer-local" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dfc9c26fe6c6f1bad818c9a976de9044dd12e1f75f1f156a801ee3e8148c1b6" dependencies = [ - "alloy-consensus 0.1.0", + "alloy-consensus", "alloy-network", "alloy-primitives 0.7.7", "alloy-signer", @@ -601,8 +547,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b51a291f949f755e6165c3ed562883175c97423703703355f4faa4b7d0a57c" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -613,14 +560,15 @@ dependencies = [ "thiserror", "tokio", "tower", + "tracing", "url", - "wasm-bindgen-futures", ] [[package]] name = "alloy-transport-http" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d65871f9f1cafe1ed25cde2f1303be83e6473e995a2d56c275ae4fcce6119c" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -633,8 +581,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7fbc8b6282ce41b01cbddef7bffb133fe6e1bf65dcd39770d45a905c051179" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -651,13 +600,15 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=f415827#f41582792950a3bf4c334c588a5497468e1f8149" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aec83fd052684556c78c54df111433493267234d82321c2236560c752f595f20" dependencies = [ "alloy-pubsub", "alloy-transport", "futures", - "http 0.2.12", + "http 1.1.0", + "rustls", "serde_json", "tokio", "tokio-tungstenite", @@ -1336,9 +1287,9 @@ checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "coins-bip32" -version = "0.8.7" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b6be4a5df2098cd811f3194f64ddb96c267606bffd9689ac7b0160097b01ad3" +checksum = "66c43ff7fd9ff522219058808a259e61423335767b1071d5b346de60d9219657" dependencies = [ "bs58", "coins-core", @@ -1352,9 +1303,9 @@ dependencies = [ [[package]] name = "coins-bip39" -version = "0.8.7" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528" +checksum = "4c4587c0b4064da887ed39a6522f577267d57e58bdd583178cd877d721b56a2e" dependencies = [ "bitvec", "coins-bip32", @@ -1368,19 +1319,18 @@ dependencies = [ [[package]] name = "coins-core" -version = "0.8.7" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" +checksum = "6b3aeeec621f4daec552e9d28befd58020a78cfc364827d06a753e8bc13c6c4b" dependencies = [ "base64 0.21.7", "bech32", "bs58", + "const-hex", "digest 0.10.7", "generic-array", - "hex", "ripemd", "serde", - "serde_derive", "sha2", "sha3", "thiserror", @@ -2106,7 +2056,6 @@ dependencies = [ "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives 0.7.7", - "alloy-signer-wallet", "comfy-table", "criterion 0.5.1", "dunce", @@ -2427,9 +2376,9 @@ dependencies = [ "alloy-genesis", "alloy-primitives 0.7.7", "alloy-provider", - "alloy-rpc-types 0.1.0", + "alloy-rpc-types", "alloy-signer", - "alloy-signer-wallet", + "alloy-signer-local", "alloy-sol-types 0.7.7", "base64 0.22.1", "const-hex", @@ -2533,7 +2482,7 @@ name = "foundry-evm-core" version = "0.3.8" dependencies = [ "alloy-chains", - "alloy-consensus 0.1.0", + "alloy-consensus", "alloy-dyn-abi", "alloy-genesis", "alloy-json-abi", @@ -2542,8 +2491,8 @@ dependencies = [ "alloy-provider", "alloy-pubsub", "alloy-rpc-client", - "alloy-rpc-types 0.1.0", - "alloy-rpc-types-engine", + "alloy-rpc-types", + "alloy-serde 0.1.4", "alloy-sol-types 0.7.7", "alloy-transport", "alloy-transport-http", @@ -2559,6 +2508,7 @@ dependencies = [ "eyre", "foundry-cheatcodes-spec", "foundry-compilers", + "foundry-fork-db", "futures", "itertools 0.12.1", "once_cell", @@ -2643,6 +2593,30 @@ dependencies = [ "yansi", ] +[[package]] +name = "foundry-fork-db" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3f9c9e02b19933218eb39c7234dcb0cc20e461258ced030f6fd6ac254a8637" +dependencies = [ + "alloy-primitives 0.7.7", + "alloy-provider", + "alloy-rpc-types", + "alloy-serde 0.1.4", + "alloy-transport", + "eyre", + "futures", + "parking_lot 0.12.3", + "revm 10.0.0", + "rustc-hash 2.0.0", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "url", +] + [[package]] name = "fragile" version = "2.0.0" @@ -3120,13 +3094,13 @@ dependencies = [ "http 1.1.0", "hyper 1.4.1", "hyper-util", - "rustls 0.23.12", + "rustls", "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls", "tower-service", - "webpki-roots 0.26.5", + "webpki-roots", ] [[package]] @@ -4483,7 +4457,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.0.0", - "rustls 0.23.12", + "rustls", "socket2", "thiserror", "tokio", @@ -4500,7 +4474,7 @@ dependencies = [ "rand", "ring", "rustc-hash 2.0.0", - "rustls 0.23.12", + "rustls", "slab", "thiserror", "tinyvec", @@ -4739,7 +4713,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.12", + "rustls", "rustls-native-certs 0.7.3", "rustls-pemfile 2.1.3", "rustls-pki-types", @@ -4748,13 +4722,13 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 1.0.1", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.26.5", + "webpki-roots", "windows-registry", ] @@ -4860,7 +4834,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eba2e187811b160463663fd71881b4e5d653720ba00be0f1e85962d4db60341c" dependencies = [ "alloy-primitives 0.7.7", - "alloy-rpc-types 0.1.4", + "alloy-rpc-types", "alloy-sol-types 0.7.7", "anstyle", "colorchoice", @@ -4897,6 +4871,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c669c9b105dbb41133c17bf7f34d29368e358a7fee8fcc289e90dbfb024dfc4" dependencies = [ "aurora-engine-modexp", + "blst", "c-kzg", "k256", "once_cell", @@ -5110,18 +5085,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - [[package]] name = "rustls" version = "0.23.12" @@ -5131,7 +5094,7 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.7", + "rustls-webpki", "subtle", "zeroize", ] @@ -5187,16 +5150,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "rustls-webpki" version = "0.102.7" @@ -5301,16 +5254,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "sec1" version = "0.7.3" @@ -6068,23 +6011,13 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.12", + "rustls", "rustls-pki-types", "tokio", ] @@ -6103,17 +6036,18 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.20.1" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" dependencies = [ "futures-util", "log", - "rustls 0.21.12", + "rustls", + "rustls-pki-types", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", "tungstenite", - "webpki-roots 0.25.4", + "webpki-roots", ] [[package]] @@ -6352,21 +6286,21 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.20.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" dependencies = [ "byteorder", "bytes", "data-encoding", - "http 0.2.12", + "http 1.1.0", "httparse", "log", "rand", - "rustls 0.21.12", + "rustls", + "rustls-pki-types", "sha1", "thiserror", - "url", "utf-8", ] @@ -6641,12 +6575,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - [[package]] name = "webpki-roots" version = "0.26.5" diff --git a/Cargo.toml b/Cargo.toml index cfd8f8060b..4be972393a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -177,6 +177,7 @@ foundry-evm-traces = { path = "crates/foundry/evm/traces" } # solc & compilation utilities foundry-block-explorers = { version = "=0.2.7", default-features = false } foundry-compilers = { version = "0.4.3", features = ["full"] } +foundry-fork-db = "=0.1.0" ## revm # no default features to avoid c-kzg @@ -188,37 +189,40 @@ revm-inspectors = { version = "0.1", features = ["serde"] } ethers-contract-abigen = { version = "2.0.14", default-features = false } ## alloy -alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-signer-wallet = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-signer-aws = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-signer-ledger = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-signer-trezor = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "f415827", default-features = false } -alloy-primitives = { version = "0.7.1", features = ["getrandom"] } -alloy-dyn-abi = "0.7.1" -alloy-json-abi = "0.7.1" -alloy-sol-types = "0.7.1" -syn-solidity = "0.7.1" +alloy-consensus = { version = "0.1.2", default-features = false } +alloy-contract = { version = "0.1.2", default-features = false } +alloy-eips = { version = "0.1.2", default-features = false } +alloy-genesis = { version = "0.1.2", default-features = false } +alloy-json-rpc = { version = "0.1.2", default-features = false } +alloy-network = { version = "0.1.2", default-features = false } +alloy-node-bindings = { version = "0.1.2", default-features = false } +alloy-provider = { version = "0.1.2", default-features = false } +alloy-pubsub = { version = "0.1.2", default-features = false } +alloy-rpc-client = { version = "0.1.2", default-features = false } +alloy-rpc-types = { version = "0.1.2", default-features = false } +alloy-serde = { version = "0.1.2", default-features = false } +alloy-signer = { version = "0.1.2", default-features = false } +alloy-signer-aws = { version = "0.1.2", default-features = false } +alloy-signer-gcp = { version = "0.1.2", default-features = false } +alloy-signer-ledger = { version = "0.1.2", default-features = false } +alloy-signer-local = { version = "0.1.2", default-features = false } +alloy-signer-trezor = { version = "0.1.2", default-features = false } +alloy-transport = { version = "0.1.2", default-features = false } +alloy-transport-http = { version = "0.1.2", default-features = false } +alloy-transport-ipc = { version = "0.1.2", default-features = false } +alloy-transport-ws = { version = "0.1.2", default-features = false } + +alloy-dyn-abi = "0.7.3" +alloy-json-abi = "0.7.3" +alloy-primitives = { version = "0.7.3", features = ["getrandom", "rand"] } +alloy-sol-macro-expander = "0.7.3" +alloy-sol-macro-input = "0.7.3" +alloy-sol-types = "0.7.3" +syn-solidity = "0.7.3" + alloy-chains = "0.1" -alloy-trie = "0.3.1" alloy-rlp = "0.3.3" +alloy-trie = "0.4.1" ## misc arrayvec = "0.7" diff --git a/crates/edr_solidity_tests/Cargo.toml b/crates/edr_solidity_tests/Cargo.toml index 92853c67d2..31be5545ac 100644 --- a/crates/edr_solidity_tests/Cargo.toml +++ b/crates/edr_solidity_tests/Cargo.toml @@ -52,8 +52,6 @@ svm = { package = "svm-rs", version = "0.5", default-features = false, features tempfile.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } -alloy-signer-wallet.workspace = true - [features] test-remote = [] diff --git a/crates/foundry/cheatcodes/Cargo.toml b/crates/foundry/cheatcodes/Cargo.toml index efcf8ae457..2eca3c7284 100644 --- a/crates/foundry/cheatcodes/Cargo.toml +++ b/crates/foundry/cheatcodes/Cargo.toml @@ -21,7 +21,7 @@ alloy-sol-types.workspace = true alloy-provider.workspace = true alloy-rpc-types.workspace = true alloy-signer.workspace = true -alloy-signer-wallet = { workspace = true, features = ["mnemonic-all-languages", "keystore"] } +alloy-signer-local = { workspace = true, features = ["mnemonic-all-languages", "keystore"] } base64.workspace = true dunce.workspace = true diff --git a/crates/foundry/cheatcodes/src/error.rs b/crates/foundry/cheatcodes/src/error.rs index 9f42855edb..933b31b2a7 100644 --- a/crates/foundry/cheatcodes/src/error.rs +++ b/crates/foundry/cheatcodes/src/error.rs @@ -2,10 +2,10 @@ use std::{borrow::Cow, fmt}; use alloy_primitives::{Address, Bytes}; use alloy_signer::Error as SignerError; -use alloy_signer_wallet::WalletError; +use alloy_signer_local::LocalSignerError; use alloy_sol_types::SolError; use edr_common::errors::FsPathError; -use foundry_evm_core::backend::DatabaseError; +use foundry_evm_core::backend::{BackendError, DatabaseError}; use k256::ecdsa::signature::Error as SignatureError; use revm::primitives::EVMError; @@ -299,6 +299,7 @@ impl_from!( FsPathError, hex::FromHexError, eyre::Error, + BackendError, DatabaseError, jsonpath_lib::JsonPathError, serde_json::Error, @@ -307,14 +308,14 @@ impl_from!( std::num::TryFromIntError, std::str::Utf8Error, std::string::FromUtf8Error, - WalletError, + LocalSignerError, SignerError, ); -impl From> for Error { +impl> From> for Error { #[inline] - fn from(err: EVMError) -> Self { - Self::display(DatabaseError::from(err)) + fn from(err: EVMError) -> Self { + Self::display(BackendError::from(err)) } } diff --git a/crates/foundry/cheatcodes/src/utils.rs b/crates/foundry/cheatcodes/src/utils.rs index 101c0b049a..ccd6592259 100644 --- a/crates/foundry/cheatcodes/src/utils.rs +++ b/crates/foundry/cheatcodes/src/utils.rs @@ -2,7 +2,7 @@ use alloy_primitives::{B256, U256}; use alloy_signer::SignerSync; -use alloy_signer_wallet::LocalWallet; +use alloy_signer_local::PrivateKeySigner; use alloy_sol_types::SolValue; use foundry_evm_core::constants::DEFAULT_CREATE2_DEPLOYER; use k256::{ecdsa::SigningKey, elliptic_curve::Curve, Secp256k1}; @@ -85,12 +85,8 @@ fn encode_vrs(sig: alloy_primitives::Signature) -> Vec { pub(super) fn sign(private_key: &U256, digest: &B256) -> Result { // The `ecrecover` precompile does not use EIP-155. No chain ID is needed. let wallet = parse_wallet(private_key)?; - let sig = wallet.sign_hash_sync(digest)?; - let recovered = sig.recover_address_from_prehash(digest)?; - - assert_eq!(recovered, wallet.address()); - + debug_assert_eq!(sig.recover_address_from_prehash(digest)?, wallet.address()); Ok(encode_vrs(sig)) } @@ -124,8 +120,8 @@ pub(super) fn parse_private_key(private_key: &U256) -> Result { SigningKey::from_bytes((&bytes).into()).map_err(Into::into) } -pub(super) fn parse_wallet(private_key: &U256) -> Result { - parse_private_key(private_key).map(LocalWallet::from) +pub(super) fn parse_wallet(private_key: &U256) -> Result { + parse_private_key(private_key).map(PrivateKeySigner::from) } #[cfg(test)] diff --git a/crates/foundry/evm/core/Cargo.toml b/crates/foundry/evm/core/Cargo.toml index aef9a066d0..0801451762 100644 --- a/crates/foundry/evm/core/Cargo.toml +++ b/crates/foundry/evm/core/Cargo.toml @@ -22,14 +22,15 @@ alloy-primitives = { workspace = true, features = ["serde", "getrandom", "arbitr alloy-genesis.workspace = true alloy-provider.workspace = true alloy-pubsub.workspace = true -alloy-rpc-types-engine.workspace = true alloy-rpc-client.workspace = true -alloy-rpc-types.workspace = true +alloy-rpc-types = { workspace = true, features = ["eth", "engine"] } +alloy-serde.workspace = true alloy-sol-types.workspace = true alloy-transport.workspace = true alloy-transport-http = { workspace = true, features = ["reqwest", "reqwest-rustls-tls"] } alloy-transport-ws.workspace = true alloy-transport-ipc.workspace = true +foundry-fork-db.workspace = true revm = { workspace = true, features = [ "std", diff --git a/crates/foundry/evm/core/src/abi/fmt/transactions.rs b/crates/foundry/evm/core/src/abi/fmt/transactions.rs index 0fbfefe218..dd24d783b5 100644 --- a/crates/foundry/evm/core/src/abi/fmt/transactions.rs +++ b/crates/foundry/evm/core/src/abi/fmt/transactions.rs @@ -1,6 +1,7 @@ //! wrappers for transactions use alloy_provider::{network::AnyNetwork, Provider}; -use alloy_rpc_types::{AnyTransactionReceipt, BlockId, WithOtherFields}; +use alloy_rpc_types::{AnyTransactionReceipt, BlockId}; +use alloy_serde::WithOtherFields; use alloy_transport::Transport; use eyre::Result; use serde::{Deserialize, Serialize}; @@ -20,7 +21,14 @@ pub struct TransactionReceiptWithRevertReason { impl TransactionReceiptWithRevertReason { /// Returns if the status of the transaction is 0 (failure) pub fn is_failure(&self) -> bool { - !self.receipt.inner.inner.inner.receipt.status + !self + .receipt + .inner + .inner + .inner + .receipt + .status + .coerce_status() } /// Updates the revert reason field using `eth_call` and returns an Err diff --git a/crates/foundry/evm/core/src/abi/fmt/ui.rs b/crates/foundry/evm/core/src/abi/fmt/ui.rs index 5866040fb1..c1def69ec3 100644 --- a/crates/foundry/evm/core/src/abi/fmt/ui.rs +++ b/crates/foundry/evm/core/src/abi/fmt/ui.rs @@ -1,11 +1,11 @@ //! Helper trait and functions to format Ethereum types. -use alloy_consensus::{AnyReceiptEnvelope, Receipt, ReceiptWithBloom, TxType}; +use alloy_consensus::{AnyReceiptEnvelope, Eip658Value, Receipt, ReceiptWithBloom, TxType}; use alloy_primitives::{hex, Address, Bloom, Bytes, FixedBytes, Uint, B256, I256, U256, U64}; use alloy_rpc_types::{ - other::OtherFields, AnyTransactionReceipt, Block, BlockTransactions, Log, Transaction, - TransactionReceipt, + AnyTransactionReceipt, Block, BlockTransactions, Log, Transaction, TransactionReceipt, }; +use alloy_serde::OtherFields; use serde::Deserialize; use crate::abi::fmt::transactions::TransactionReceiptWithRevertReason; @@ -148,8 +148,13 @@ impl UIfmt for [u8] { } } -pub fn pretty_status(status: bool) -> String { - if status { "1 (success)" } else { "0 (failed)" }.to_string() +impl UIfmt for Eip658Value { + fn pretty(&self) -> String { + match self { + Self::Eip658(status) => if *status { "1 (success)" } else { "0 (failed)" }.to_string(), + Self::PostState(state) => state.pretty(), + } + } } impl UIfmt for AnyTransactionReceipt { @@ -215,7 +220,7 @@ blobGasUsed {}", serde_json::to_string(&logs).unwrap(), logs_bloom.pretty(), state_root.pretty(), - pretty_status(*status), + status.pretty(), transaction_hash.pretty(), transaction_index.pretty(), transaction_type, @@ -446,9 +451,9 @@ pub fn get_pretty_tx_receipt_attr( ), "logsBloom" | "logs_bloom" => Some(receipt.receipt.inner.inner.inner.logs_bloom.pretty()), "root" | "stateRoot" | "state_root " => Some(receipt.receipt.state_root.pretty()), - "status" | "statusCode" | "status_code" => Some(pretty_status( - receipt.receipt.inner.inner.inner.receipt.status, - )), + "status" | "statusCode" | "status_code" => { + Some(receipt.receipt.inner.inner.inner.receipt.status.pretty()) + } "transactionHash" | "transaction_hash" => Some(receipt.receipt.transaction_hash.pretty()), "transactionIndex" | "transaction_index" => { Some(receipt.receipt.transaction_index.pretty()) diff --git a/crates/foundry/evm/core/src/backend/cow.rs b/crates/foundry/evm/core/src/backend/cow.rs index dc8fddd02e..f247436cee 100644 --- a/crates/foundry/evm/core/src/backend/cow.rs +++ b/crates/foundry/evm/core/src/backend/cow.rs @@ -5,6 +5,7 @@ use std::{borrow::Cow, collections::BTreeMap}; use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, B256, U256}; use eyre::WrapErr; +use foundry_fork_db::DatabaseError; use revm::{ db::DatabaseRef, primitives::{ @@ -14,10 +15,10 @@ use revm::{ Database, DatabaseCommit, JournaledState, }; +use super::BackendError; use crate::{ backend::{ - diagnostic::RevertDiagnostic, error::DatabaseError, Backend, DatabaseExt, LocalForkId, - RevertSnapshotAction, + diagnostic::RevertDiagnostic, Backend, DatabaseExt, LocalForkId, RevertSnapshotAction, }, fork::{CreateFork, ForkId}, InspectorExt, @@ -236,7 +237,7 @@ impl<'a> DatabaseExt for CowBackend<'a> { &mut self, allocs: &BTreeMap, journaled_state: &mut JournaledState, - ) -> Result<(), DatabaseError> { + ) -> Result<(), BackendError> { self.backend_mut(&Env::default()) .load_allocs(allocs, journaled_state) } diff --git a/crates/foundry/evm/core/src/backend/error.rs b/crates/foundry/evm/core/src/backend/error.rs index 920d1cdece..d3bf24e87e 100644 --- a/crates/foundry/evm/core/src/backend/error.rs +++ b/crates/foundry/evm/core/src/backend/error.rs @@ -1,46 +1,24 @@ -use std::{ - convert::Infallible, - sync::{mpsc::RecvError, Arc}, -}; +use std::convert::Infallible; -use alloy_primitives::{Address, B256, U256}; -use alloy_rpc_types::BlockId; -use futures::channel::mpsc::{SendError, TrySendError}; +use alloy_primitives::Address; +pub use foundry_fork_db::{DatabaseError, DatabaseResult}; use revm::primitives::EVMError; /// Result alias with `DatabaseError` as error -pub type DatabaseResult = Result; +pub type BackendResult = Result; /// Errors that can happen when working with [`revm::Database`] #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] -pub enum DatabaseError { +pub enum BackendError { #[error("{0}")] Message(String), #[error("cheatcodes are not enabled for {0}; see `vm.allowCheatcodes(address)`")] NoCheats(Address), + #[error(transparent)] + Backend(#[from] DatabaseError), #[error("failed to fetch account info for {0}")] MissingAccount(Address), - #[error("missing bytecode for code hash {0}")] - MissingCode(B256), - #[error(transparent)] - Recv(#[from] RecvError), - #[error(transparent)] - Send(#[from] SendError), - #[error("failed to get account for {0}: {1}")] - GetAccount(Address, Arc), - #[error("failed to get storage for {0} at {1}: {2}")] - GetStorage(Address, U256, Arc), - #[error("failed to get block hash for {0}: {1}")] - GetBlockHash(u64, Arc), - #[error("failed to get full block for {0:?}: {1}")] - GetFullBlock(BlockId, Arc), - #[error("block {0:?} does not exist")] - BlockNotFound(BlockId), - #[error("failed to get transaction {0}: {1}")] - GetTransaction(B256, Arc), - #[error("transaction {0} not found")] - TransactionNotFound(B256), #[error( "CREATE2 Deployer (0x4e59b44847b379578588920ca78fbf26c0b4956c) not present on this chain.\n\ For a production environment, you can deploy it using the pre-signed transaction from \ @@ -52,62 +30,25 @@ pub enum DatabaseError { Other(String), } -impl DatabaseError { +impl BackendError { /// Create a new error with a message pub fn msg(msg: impl Into) -> Self { - DatabaseError::Message(msg.into()) + BackendError::Message(msg.into()) } /// Create a new error with a message pub fn display(msg: impl std::fmt::Display) -> Self { - DatabaseError::Message(msg.to_string()) - } - - #[allow(clippy::match_same_arms)] - fn get_rpc_error(&self) -> Option<&eyre::Error> { - match self { - Self::GetAccount(_, err) => Some(err), - Self::GetStorage(_, _, err) => Some(err), - Self::GetBlockHash(_, err) => Some(err), - Self::GetFullBlock(_, err) => Some(err), - Self::GetTransaction(_, err) => Some(err), - // Enumerate explicitly to make sure errors are updated if a new one is added. - Self::NoCheats(_) - | Self::MissingAccount(_) - | Self::MissingCode(_) - | Self::Recv(_) - | Self::Send(_) - | Self::Message(_) - | Self::BlockNotFound(_) - | Self::TransactionNotFound(_) - | Self::MissingCreate2Deployer => None, - DatabaseError::Other(_) => None, - } - } - - /// Whether the error is potentially caused by the user forking from an - /// older block in a non-archive node. - pub fn is_possibly_non_archive_node_error(&self) -> bool { - static GETH_MESSAGE: &str = "missing trie node"; - - self.get_rpc_error() - .is_some_and(|err| err.to_string().to_lowercase().contains(GETH_MESSAGE)) + BackendError::Message(msg.to_string()) } } -impl From for DatabaseError { +impl From for BackendError { fn from(value: tokio::task::JoinError) -> Self { - DatabaseError::display(value) - } -} - -impl From> for DatabaseError { - fn from(value: TrySendError) -> Self { - value.into_send_error().into() + BackendError::display(value) } } -impl From for DatabaseError { +impl From for BackendError { fn from(value: Infallible) -> Self { match value {} } @@ -115,11 +56,13 @@ impl From for DatabaseError { // Note: this is mostly necessary to use some revm internals that return an // [EVMError] -impl From> for DatabaseError { - fn from(err: EVMError) -> Self { +impl> From> for BackendError { + fn from(err: EVMError) -> Self { match err { - EVMError::Database(err) => err, - err => DatabaseError::Other(err.to_string()), + EVMError::Database(err) => err.into(), + EVMError::Custom(err) | EVMError::Precompile(err) => Self::msg(err), + EVMError::Header(err) => Self::msg(err.to_string()), + EVMError::Transaction(err) => Self::msg(err.to_string()), } } } diff --git a/crates/foundry/evm/core/src/backend/in_memory_db.rs b/crates/foundry/evm/core/src/backend/in_memory_db.rs index 6430544dd3..781ea7c011 100644 --- a/crates/foundry/evm/core/src/backend/in_memory_db.rs +++ b/crates/foundry/evm/core/src/backend/in_memory_db.rs @@ -1,12 +1,13 @@ //! The in memory DB use alloy_primitives::{Address, B256, U256}; +use foundry_fork_db::DatabaseError; use revm::{ db::{CacheDB, DatabaseRef, EmptyDB}, primitives::{Account, AccountInfo, Bytecode, HashMap as Map}, Database, DatabaseCommit, }; -use crate::{backend::error::DatabaseError, snapshot::Snapshots}; +use crate::snapshot::Snapshots; /// Type alias for an in memory database /// diff --git a/crates/foundry/evm/core/src/backend/mod.rs b/crates/foundry/evm/core/src/backend/mod.rs index 48e8a1d727..0f060abc5b 100644 --- a/crates/foundry/evm/core/src/backend/mod.rs +++ b/crates/foundry/evm/core/src/backend/mod.rs @@ -7,8 +7,10 @@ use std::{ use alloy_genesis::GenesisAccount; use alloy_primitives::{address, b256, keccak256, Address, B256, U256}; -use alloy_rpc_types::{Block, BlockNumberOrTag, BlockTransactions, Transaction, WithOtherFields}; +use alloy_rpc_types::{Block, BlockNumberOrTag, BlockTransactions, Transaction}; +use alloy_serde::WithOtherFields; use eyre::Context; +pub use foundry_fork_db::{cache::BlockchainDbMeta, BlockchainDb, SharedBackend}; use revm::{ db::{CacheDB, DatabaseRef}, inspectors::NoOpInspector, @@ -22,7 +24,7 @@ use revm::{ use crate::{ constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, TEST_CONTRACT_ADDRESS}, - fork::{CreateFork, ForkId, MultiFork, SharedBackend}, + fork::{CreateFork, ForkId, MultiFork}, snapshot::Snapshots, utils::configure_tx_env, InspectorExt, @@ -32,7 +34,7 @@ mod diagnostic; pub use diagnostic::RevertDiagnostic; mod error; -pub use error::{DatabaseError, DatabaseResult}; +pub use error::{BackendError, BackendResult, DatabaseError, DatabaseResult}; mod cow; pub use cow::CowBackend; @@ -292,7 +294,7 @@ pub trait DatabaseExt: Database { &mut self, allocs: &BTreeMap, journaled_state: &mut JournaledState, - ) -> Result<(), DatabaseError>; + ) -> Result<(), BackendError>; /// Returns true if the given account is currently marked as persistent. fn is_persistent(&self, acc: &Address) -> bool; @@ -339,16 +341,16 @@ pub trait DatabaseExt: Database { /// Ensures that `account` is allowed to execute cheatcodes /// /// Returns an error if [`Self::has_cheatcode_access`] returns `false` - fn ensure_cheatcode_access(&self, account: &Address) -> Result<(), DatabaseError> { + fn ensure_cheatcode_access(&self, account: &Address) -> Result<(), BackendError> { if !self.has_cheatcode_access(account) { - return Err(DatabaseError::NoCheats(*account)); + return Err(BackendError::NoCheats(*account)); } Ok(()) } /// Same as [`Self::ensure_cheatcode_access()`] but only enforces it if the /// backend is currently in forking mode - fn ensure_cheatcode_access_forking_mode(&self, account: &Address) -> Result<(), DatabaseError> { + fn ensure_cheatcode_access_forking_mode(&self, account: &Address) -> Result<(), BackendError> { if self.is_forked_mode() { return self.ensure_cheatcode_access(account); } @@ -535,19 +537,11 @@ impl Backend { slot: U256, value: U256, ) -> Result<(), DatabaseError> { - let ret = if let Some(db) = self.active_fork_db_mut() { + if let Some(db) = self.active_fork_db_mut() { db.insert_account_storage(address, slot, value) } else { self.mem_db.insert_account_storage(address, slot, value) - }; - - // We don't apply any mutations here, so it's ok to optimize out. - #[allow(clippy::debug_assert_with_mut_call)] - { - debug_assert!(self.storage(address, slot).unwrap() == value); } - - ret } /// Completely replace an account's storage without overriding account info. @@ -875,7 +869,7 @@ impl Backend { /// This account data then would not match the account data of a fork if it /// exists. So when the first fork is initialized we replace these /// accounts with the actual account as it exists on the fork. - fn prepare_init_journal_state(&mut self) -> Result<(), DatabaseError> { + fn prepare_init_journal_state(&mut self) -> Result<(), BackendError> { let loaded_accounts = self .fork_init_journaled_state .state @@ -906,7 +900,7 @@ impl Backend { // otherwise we need to replace the account's info with the one from the fork's // database let fork_account = Database::basic(&mut fork.db, loaded_account)? - .ok_or(DatabaseError::MissingAccount(loaded_account))?; + .ok_or(BackendError::MissingAccount(loaded_account))?; init_account.info = fork_account; } fork.journaled_state = journaled_state; @@ -937,7 +931,7 @@ impl Backend { let number = block .header .number - .ok_or_else(|| DatabaseError::BlockNotFound(BlockNumberOrTag::Latest.into()))?; + .ok_or_else(|| BackendError::msg("missing block number"))?; Ok((number, block)) } @@ -1151,7 +1145,7 @@ impl DatabaseExt for Backend { // Initialize caller with its fork info if let Some(mut acc) = caller_account { let fork_account = Database::basic(&mut target_fork.db, caller)? - .ok_or(DatabaseError::MissingAccount(caller))?; + .ok_or(BackendError::MissingAccount(caller))?; acc.info = fork_account; target_fork.journaled_state.state.insert(caller, acc); @@ -1413,7 +1407,7 @@ impl DatabaseExt for Backend { &mut self, allocs: &BTreeMap, journaled_state: &mut JournaledState, - ) -> Result<(), DatabaseError> { + ) -> Result<(), BackendError> { // Loop through all of the allocs defined in the map and commit them to the // journal. for (addr, acc) in allocs.iter() { @@ -2041,3 +2035,82 @@ fn apply_state_changeset( fn is_known_system_sender(sender: Address) -> bool { [ARBITRUM_SENDER, OPTIMISM_SYSTEM_ADDRESS].contains(&sender) } + +#[cfg(test)] +mod tests { + use std::collections::BTreeSet; + + use alloy_chains::NamedChain; + use alloy_primitives::{Address, U256}; + use alloy_provider::Provider; + use edr_test_utils::env::get_alchemy_url; + use foundry_fork_db::cache::{BlockchainDb, BlockchainDbMeta}; + use revm::DatabaseRef; + use tempfile::tempdir; + + use crate::{ + backend::Backend, + fork::{provider::get_http_provider, CreateFork}, + opts::EvmOpts, + }; + + #[tokio::test(flavor = "multi_thread")] + async fn can_read_write_cache() { + let endpoint = get_alchemy_url(); + let cache_dir = tempdir().expect("Should create tempdir"); + + let provider = get_http_provider(&endpoint); + + let block_num = provider.get_block_number().await.unwrap(); + + let evm_opts = EvmOpts { + fork_block_number: Some(block_num), + ..EvmOpts::default() + }; + + let (env, _block) = evm_opts.fork_evm_env(&endpoint).await.unwrap(); + + let fork = CreateFork { + rpc_cache_path: Some(cache_dir.path().into()), + url: endpoint, + env: env.clone(), + evm_opts, + }; + + let backend = Backend::spawn(Some(fork.clone())); + + // some rng contract from etherscan + let address: Address = "63091244180ae240c87d1f528f5f269134cb07b3".parse().unwrap(); + + let idx = U256::from(0u64); + let _value = backend.storage_ref(address, idx); + let _account = backend.basic_ref(address); + + // fill some slots + let num_slots = 10u64; + for idx in 1..num_slots { + let _ = backend.storage_ref(address, U256::from(idx)); + } + drop(backend); + + let meta = BlockchainDbMeta { + cfg_env: env.cfg, + block_env: env.block, + hosts: BTreeSet::default(), + }; + + let db = BlockchainDb::new( + meta, + Some( + fork.block_cache_dir(NamedChain::Mainnet, block_num) + .expect("Rpc cache path should be set"), + ), + ); + assert!(db.accounts().read().contains_key(&address)); + assert!(db.storage().read().contains_key(&address)); + assert_eq!( + db.storage().read().get(&address).unwrap().len(), + num_slots as usize + ); + } +} diff --git a/crates/foundry/evm/core/src/fork/backend.rs b/crates/foundry/evm/core/src/fork/backend.rs deleted file mode 100644 index a5b9601d57..0000000000 --- a/crates/foundry/evm/core/src/fork/backend.rs +++ /dev/null @@ -1,899 +0,0 @@ -//! Smart caching and deduplication of requests when using a forking provider -use std::{ - collections::{hash_map::Entry, HashMap, VecDeque}, - future::IntoFuture, - marker::PhantomData, - pin::Pin, - sync::{ - mpsc::{channel as oneshot_channel, Sender as OneshotSender}, - Arc, - }, -}; - -use alloy_primitives::{keccak256, Address, Bytes, B256, U256}; -use alloy_provider::{network::AnyNetwork, Provider}; -use alloy_rpc_types::{Block, BlockId, Transaction, WithOtherFields}; -use alloy_transport::Transport; -use eyre::WrapErr; -use futures::{ - channel::mpsc::{channel, Receiver, Sender}, - stream::Stream, - task::{Context, Poll}, - Future, FutureExt, -}; -use revm::{ - db::DatabaseRef, - primitives::{AccountInfo, Bytecode, KECCAK_EMPTY}, -}; -use rustc_hash::FxHashMap; - -use crate::{ - backend::{DatabaseError, DatabaseResult}, - fork::{cache::FlushJsonBlockCacheDB, BlockchainDb}, -}; - -// Various future/request type aliases - -type AccountFuture = - Pin, Address)> + Send>>; -type StorageFuture = Pin, Address, U256)> + Send>>; -type BlockHashFuture = Pin, u64)> + Send>>; -type FullBlockFuture = - Pin, Err>, BlockId)> + Send>>; -type TransactionFuture = Pin< - Box< - dyn Future< - Output = ( - TransactionSender, - Result, Err>, - B256, - ), - > + Send, - >, ->; - -type AccountInfoSender = OneshotSender>; -type StorageSender = OneshotSender>; -type BlockHashSender = OneshotSender>; -type FullBlockSender = OneshotSender>; -type TransactionSender = OneshotSender>>; - -/// Logged when an error is indicative that the user is trying to fork from a -/// non-archive node. -const NON_ARCHIVE_NODE_WARNING: &str = "\ -It looks like you're trying to fork from an older block with a non-archive node which is not \ -supported. Please try to change your RPC url to an archive node if the issue persists."; - -/// Request variants that are executed by the provider -enum ProviderRequest { - Account(AccountFuture), - Storage(StorageFuture), - BlockHash(BlockHashFuture), - FullBlock(FullBlockFuture), - Transaction(TransactionFuture), -} - -/// The Request type the Backend listens for -#[derive(Debug)] -enum BackendRequest { - /// Fetch the account info - Basic(Address, AccountInfoSender), - /// Fetch a storage slot - Storage(Address, U256, StorageSender), - /// Fetch a block hash - BlockHash(u64, BlockHashSender), - /// Fetch an entire block with transactions - FullBlock(BlockId, FullBlockSender), - /// Fetch a transaction - Transaction(B256, TransactionSender), - /// Sets the pinned block to fetch data from - SetPinnedBlock(BlockId), -} - -/// Handles an internal provider and listens for requests. -/// -/// This handler will remain active as long as it is reachable (request channel -/// still open) and requests are in progress. -#[must_use = "futures do nothing unless polled"] -pub struct BackendHandler { - provider: P, - transport: PhantomData, - /// Stores all the data. - db: BlockchainDb, - /// Requests currently in progress - pending_requests: Vec>, - /// Listeners that wait for a `get_account` related response - account_requests: HashMap>, - /// Listeners that wait for a `get_storage_at` response - storage_requests: HashMap<(Address, U256), Vec>, - /// Listeners that wait for a `get_block` response - block_requests: FxHashMap>, - /// Incoming commands. - incoming: Receiver, - /// unprocessed queued requests - queued_requests: VecDeque, - /// The block to fetch data from. - // This is an `Option` so that we can have less code churn in the functions below - block_id: Option, -} - -impl BackendHandler -where - T: Transport + Clone, - P: Provider + Clone + Unpin + 'static, -{ - fn new( - provider: P, - db: BlockchainDb, - rx: Receiver, - block_id: Option, - ) -> Self { - Self { - provider, - db, - pending_requests: Vec::default(), - account_requests: HashMap::default(), - storage_requests: HashMap::default(), - block_requests: FxHashMap::default(), - queued_requests: VecDeque::default(), - incoming: rx, - block_id, - transport: PhantomData, - } - } - - /// handle the request in queue in the future. - /// - /// We always check: - /// 1. if the requested value is already stored in the cache, then answer - /// the sender - /// 2. otherwise, fetch it via the provider but check if a request for that - /// value is already in - /// progress (e.g. another Sender just requested the same account) - fn on_request(&mut self, req: BackendRequest) { - match req { - BackendRequest::Basic(addr, sender) => { - trace!(target: "backendhandler", "received request basic address={:?}", addr); - let acc = self.db.accounts().read().get(&addr).cloned(); - if let Some(basic) = acc { - let _ = sender.send(Ok(basic)); - } else { - self.request_account(addr, sender); - } - } - BackendRequest::BlockHash(number, sender) => { - let hash = self - .db - .block_hashes() - .read() - .get(&U256::from(number)) - .cloned(); - if let Some(hash) = hash { - let _ = sender.send(Ok(hash)); - } else { - self.request_hash(number, sender); - } - } - BackendRequest::FullBlock(number, sender) => { - self.request_full_block(number, sender); - } - BackendRequest::Transaction(tx, sender) => { - self.request_transaction(tx, sender); - } - BackendRequest::Storage(addr, idx, sender) => { - // account is already stored in the cache - let value = self - .db - .storage() - .read() - .get(&addr) - .and_then(|acc| acc.get(&idx).copied()); - if let Some(value) = value { - let _ = sender.send(Ok(value)); - } else { - // account present but not storage -> fetch storage - self.request_account_storage(addr, idx, sender); - } - } - BackendRequest::SetPinnedBlock(block_id) => { - self.block_id = Some(block_id); - } - } - } - - /// process a request for account's storage - fn request_account_storage(&mut self, address: Address, idx: U256, listener: StorageSender) { - match self.storage_requests.entry((address, idx)) { - Entry::Occupied(mut entry) => { - entry.get_mut().push(listener); - } - Entry::Vacant(entry) => { - trace!(target: "backendhandler", %address, %idx, "preparing storage request"); - entry.insert(vec![listener]); - let provider = self.provider.clone(); - let block_id = self.block_id.unwrap_or_default(); - let fut = Box::pin(async move { - let storage = provider - .get_storage_at(address, idx) - .block_id(block_id) - .await - .map_err(Into::into); - (storage, address, idx) - }); - self.pending_requests.push(ProviderRequest::Storage(fut)); - } - } - } - - /// returns the future that fetches the account data - fn get_account_req(&self, address: Address) -> ProviderRequest { - trace!(target: "backendhandler", "preparing account request, address={:?}", address); - let provider = self.provider.clone(); - let block_id = self.block_id.unwrap_or_default(); - let fut = Box::pin(async move { - let balance = provider - .get_balance(address) - .block_id(block_id) - .into_future(); - let nonce = provider - .get_transaction_count(address) - .block_id(block_id) - .into_future(); - let code = provider - .get_code_at(address) - .block_id(block_id) - .into_future(); - let resp = tokio::try_join!(balance, nonce, code).map_err(Into::into); - (resp, address) - }); - ProviderRequest::Account(fut) - } - - /// process a request for an account - fn request_account(&mut self, address: Address, listener: AccountInfoSender) { - match self.account_requests.entry(address) { - Entry::Occupied(mut entry) => { - entry.get_mut().push(listener); - } - Entry::Vacant(entry) => { - entry.insert(vec![listener]); - self.pending_requests.push(self.get_account_req(address)); - } - } - } - - /// process a request for an entire block - fn request_full_block(&mut self, number: BlockId, sender: FullBlockSender) { - let provider = self.provider.clone(); - let fut = Box::pin(async move { - let block = provider - .get_block(number, true) - .await - .wrap_err("could not fetch block {number:?}"); - (sender, block, number) - }); - - self.pending_requests.push(ProviderRequest::FullBlock(fut)); - } - - /// process a request for a transactions - fn request_transaction(&mut self, tx: B256, sender: TransactionSender) { - let provider = self.provider.clone(); - let fut = Box::pin(async move { - let block = provider - .get_transaction_by_hash(tx) - .await - .wrap_err_with(|| format!("could not get transaction {tx}")) - .and_then(|maybe| { - maybe.ok_or_else(|| eyre::eyre!("could not get transaction {tx}")) - }); - (sender, block, tx) - }); - - self.pending_requests - .push(ProviderRequest::Transaction(fut)); - } - - /// process a request for a block hash - fn request_hash(&mut self, number: u64, listener: BlockHashSender) { - match self.block_requests.entry(number) { - Entry::Occupied(mut entry) => { - entry.get_mut().push(listener); - } - Entry::Vacant(entry) => { - trace!(target: "backendhandler", number, "preparing block hash request"); - entry.insert(vec![listener]); - let provider = self.provider.clone(); - let fut = Box::pin(async move { - let block = provider - .get_block_by_number(number.into(), false) - .await - .wrap_err("failed to get block"); - - let block_hash = match block { - Ok(Some(block)) => Ok(block - .header - .hash - .expect("empty block hash on mined block, this should never happen")), - Ok(None) => { - warn!(target: "backendhandler", ?number, "block not found"); - // if no block was returned then the block does not exist, in which case - // we return empty hash - Ok(KECCAK_EMPTY) - } - Err(err) => { - error!(target: "backendhandler", %err, ?number, "failed to get block"); - Err(err) - } - }; - (block_hash, number) - }); - self.pending_requests.push(ProviderRequest::BlockHash(fut)); - } - } - } -} - -impl Future for BackendHandler -where - T: Transport + Clone + Unpin, - P: Provider + Clone + Unpin + 'static, -{ - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let pin = self.get_mut(); - loop { - // Drain queued requests first. - while let Some(req) = pin.queued_requests.pop_front() { - pin.on_request(req); - } - - // receive new requests to delegate to the underlying provider - loop { - match Pin::new(&mut pin.incoming).poll_next(cx) { - Poll::Ready(Some(req)) => { - pin.queued_requests.push_back(req); - } - Poll::Ready(None) => { - trace!(target: "backendhandler", "last sender dropped, ready to drop (&flush cache)"); - return Poll::Ready(()); - } - Poll::Pending => break, - } - } - - // poll all requests in progress - for n in (0..pin.pending_requests.len()).rev() { - let mut request = pin.pending_requests.swap_remove(n); - match &mut request { - ProviderRequest::Account(fut) => { - if let Poll::Ready((resp, addr)) = fut.poll_unpin(cx) { - // get the response - let (balance, nonce, code) = match resp { - Ok(res) => res, - Err(err) => { - let err = Arc::new(err); - if let Some(listeners) = pin.account_requests.remove(&addr) { - for l in listeners { - let _ = l.send(Err(DatabaseError::GetAccount( - addr, - Arc::clone(&err), - ))); - } - } - continue; - } - }; - - // convert it to revm-style types - let (code, code_hash) = if !code.is_empty() { - (code.clone(), keccak256(&code)) - } else { - (Bytes::default(), KECCAK_EMPTY) - }; - - // update the cache - let acc = AccountInfo { - nonce, - balance, - code: Some(Bytecode::new_raw(code)), - code_hash, - }; - pin.db.accounts().write().insert(addr, acc.clone()); - - // notify all listeners - if let Some(listeners) = pin.account_requests.remove(&addr) { - for l in listeners { - let _ = l.send(Ok(acc.clone())); - } - } - continue; - } - } - ProviderRequest::Storage(fut) => { - if let Poll::Ready((resp, addr, idx)) = fut.poll_unpin(cx) { - let value = match resp { - Ok(value) => value, - Err(err) => { - // notify all listeners - let err = Arc::new(err); - if let Some(listeners) = - pin.storage_requests.remove(&(addr, idx)) - { - for l in listeners { - let _ = l.send(Err(DatabaseError::GetStorage( - addr, - idx, - Arc::clone(&err), - ))); - } - } - continue; - } - }; - - // update the cache - pin.db - .storage() - .write() - .entry(addr) - .or_default() - .insert(idx, value); - - // notify all listeners - if let Some(listeners) = pin.storage_requests.remove(&(addr, idx)) { - for l in listeners { - let _ = l.send(Ok(value)); - } - } - continue; - } - } - ProviderRequest::BlockHash(fut) => { - if let Poll::Ready((block_hash, number)) = fut.poll_unpin(cx) { - let value = match block_hash { - Ok(value) => value, - Err(err) => { - let err = Arc::new(err); - // notify all listeners - if let Some(listeners) = pin.block_requests.remove(&number) { - for l in listeners { - let _ = l.send(Err(DatabaseError::GetBlockHash( - number, - Arc::clone(&err), - ))); - } - } - continue; - } - }; - - // update the cache - pin.db - .block_hashes() - .write() - .insert(U256::from(number), value); - - // notify all listeners - if let Some(listeners) = pin.block_requests.remove(&number) { - for l in listeners { - let _ = l.send(Ok(value)); - } - } - continue; - } - } - ProviderRequest::FullBlock(fut) => { - if let Poll::Ready((sender, resp, number)) = fut.poll_unpin(cx) { - let msg = match resp { - Ok(Some(block)) => Ok(block), - Ok(None) => Err(DatabaseError::BlockNotFound(number)), - Err(err) => { - let err = Arc::new(err); - Err(DatabaseError::GetFullBlock(number, err)) - } - }; - let _ = sender.send(msg); - continue; - } - } - ProviderRequest::Transaction(fut) => { - if let Poll::Ready((sender, tx, tx_hash)) = fut.poll_unpin(cx) { - let msg = match tx { - Ok(tx) => Ok(tx), - Err(err) => { - let err = Arc::new(err); - Err(DatabaseError::GetTransaction(tx_hash, err)) - } - }; - let _ = sender.send(msg); - continue; - } - } - } - // not ready, insert and poll again - pin.pending_requests.push(request); - } - - // If no new requests have been queued, break to - // be polled again later. - if pin.queued_requests.is_empty() { - return Poll::Pending; - } - } - } -} - -/// A cloneable backend type that shares access to the backend data with all its -/// clones. -/// -/// This backend type is connected to the `BackendHandler` via a mpsc channel. -/// The `BackendHandler` is spawned on a tokio task and listens for incoming -/// commands on the receiver half of the channel. A `SharedBackend` holds a -/// sender for that channel, which is `Clone`, so there can be -/// multiple `SharedBackend`s communicating with the same `BackendHandler`, -/// hence this `Backend` type is thread safe. -/// -/// All `Backend` trait functions are delegated as a `BackendRequest` via the -/// channel to the `BackendHandler`. All `BackendRequest` variants include a -/// sender half of an additional channel that is used by the `BackendHandler` to -/// send the result of an executed `BackendRequest` back to `SharedBackend`. -/// -/// The `BackendHandler` holds a `Provider` to look up missing accounts or -/// storage slots from remote (e.g. infura). It detects duplicate requests from -/// multiple `SharedBackend`s and bundles them together, so that always only one -/// provider request is executed. For example, there are two `SharedBackend`s, -/// `A` and `B`, both request the basic account info of account `0xasd9sa7d...` -/// at the same time. After the `BackendHandler` receives the request from `A`, -/// it sends a new provider request to the provider's endpoint, then it reads -/// the identical request from `B` and simply adds it as an additional listener -/// for the request already in progress, instead of sending another one. So that -/// after the provider returns the response all listeners (`A` and `B`) get -/// notified. -// **Note**: the implementation makes use of [tokio::task::block_in_place()] when interacting with -// the underlying [BackendHandler] which runs on a separate spawned tokio task. -// [tokio::task::block_in_place()] -// > Runs the provided blocking function on the current thread without blocking the executor. -// This prevents issues (hangs) we ran into were the [SharedBackend] itself is called from a spawned -// task. -#[derive(Clone, Debug)] -pub struct SharedBackend { - /// channel used for sending commands related to database operations - backend: Sender, - /// Ensures that the underlying cache gets flushed once the last - /// `SharedBackend` is dropped. - /// - /// There is only one instance of the type, so as soon as the last - /// `SharedBackend` is deleted, `FlushJsonBlockCacheDB` is also deleted - /// and the cache is flushed. - cache: Arc, -} - -impl SharedBackend { - /// _Spawns_ a new `BackendHandler` on a `tokio::task` that listens for - /// requests from any `SharedBackend`. Missing values get inserted in - /// the `db`. - /// - /// The spawned `BackendHandler` finishes once the last `SharedBackend` - /// connected to it is dropped. - /// - /// NOTE: this should be called with `Arc` - pub async fn spawn_backend( - provider: P, - db: BlockchainDb, - pin_block: Option, - ) -> Self - where - T: Transport + Clone + Unpin, - P: Provider + Unpin + 'static + Clone, - { - let (shared, handler) = Self::new(provider, db, pin_block); - // spawn the provider handler to a task - trace!(target: "backendhandler", "spawning Backendhandler task"); - tokio::spawn(handler); - shared - } - - /// Same as `Self::spawn_backend` but spawns the `BackendHandler` on a - /// separate `std::thread` in its own `tokio::Runtime` - pub fn spawn_backend_thread( - provider: P, - db: BlockchainDb, - pin_block: Option, - ) -> Self - where - T: Transport + Clone + Unpin, - P: Provider + Unpin + 'static + Clone, - { - let (shared, handler) = Self::new(provider, db, pin_block); - - // spawn a light-weight thread with a thread-local async runtime just for - // sending and receiving data from the remote client - std::thread::Builder::new() - .name("fork-backend".into()) - .spawn(move || { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("failed to build tokio runtime"); - - rt.block_on(handler); - }) - .expect("failed to spawn thread"); - trace!(target: "backendhandler", "spawned Backendhandler thread"); - - shared - } - - /// Returns a new `SharedBackend` and the `BackendHandler` - pub fn new( - provider: P, - db: BlockchainDb, - pin_block: Option, - ) -> (Self, BackendHandler) - where - T: Transport + Clone + Unpin, - P: Provider + Unpin + 'static + Clone, - { - let (backend, backend_rx) = channel(1); - let cache = Arc::new(FlushJsonBlockCacheDB(Arc::clone(db.cache()))); - let handler = BackendHandler::new(provider, db, backend_rx, pin_block); - (Self { backend, cache }, handler) - } - - /// Updates the pinned block to fetch data from - pub fn set_pinned_block(&self, block: impl Into) -> eyre::Result<()> { - let req = BackendRequest::SetPinnedBlock(block.into()); - self.backend - .clone() - .try_send(req) - .map_err(|e| eyre::eyre!("{:?}", e)) - } - - /// Returns the full block for the given block identifier - pub fn get_full_block(&self, block: impl Into) -> DatabaseResult { - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot_channel(); - let req = BackendRequest::FullBlock(block.into(), sender); - self.backend.clone().try_send(req)?; - rx.recv()? - }) - } - - /// Returns the transaction for the hash - pub fn get_transaction(&self, tx: B256) -> DatabaseResult> { - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot_channel(); - let req = BackendRequest::Transaction(tx, sender); - self.backend.clone().try_send(req)?; - rx.recv()? - }) - } - - fn do_get_basic(&self, address: Address) -> DatabaseResult> { - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot_channel(); - let req = BackendRequest::Basic(address, sender); - self.backend.clone().try_send(req)?; - rx.recv()?.map(Some) - }) - } - - fn do_get_storage(&self, address: Address, index: U256) -> DatabaseResult { - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot_channel(); - let req = BackendRequest::Storage(address, index, sender); - self.backend.clone().try_send(req)?; - rx.recv()? - }) - } - - fn do_get_block_hash(&self, number: u64) -> DatabaseResult { - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot_channel(); - let req = BackendRequest::BlockHash(number, sender); - self.backend.clone().try_send(req)?; - rx.recv()? - }) - } - - /// Flushes the DB to disk if caching is enabled - pub(crate) fn flush_cache(&self) { - self.cache.0.flush(); - } -} - -impl DatabaseRef for SharedBackend { - type Error = DatabaseError; - - fn basic_ref(&self, address: Address) -> Result, Self::Error> { - trace!(target: "sharedbackend", %address, "request basic"); - self.do_get_basic(address).map_err(|err| { - error!(target: "sharedbackend", %err, %address, "Failed to send/recv `basic`"); - if err.is_possibly_non_archive_node_error() { - error!(target: "sharedbackend", "{NON_ARCHIVE_NODE_WARNING}"); - } - err - }) - } - - fn code_by_hash_ref(&self, hash: B256) -> Result { - Err(DatabaseError::MissingCode(hash)) - } - - fn storage_ref(&self, address: Address, index: U256) -> Result { - trace!(target: "sharedbackend", "request storage {:?} at {:?}", address, index); - self.do_get_storage(address, index).map_err(|err| { - error!(target: "sharedbackend", %err, %address, %index, "Failed to send/recv `storage`"); - if err.is_possibly_non_archive_node_error() { - error!(target: "sharedbackend", "{NON_ARCHIVE_NODE_WARNING}"); - } - err - }) - } - - fn block_hash_ref(&self, number: U256) -> Result { - if number > U256::from(u64::MAX) { - return Ok(KECCAK_EMPTY); - } - let number: U256 = number; - let number = number.to(); - trace!(target: "sharedbackend", "request block hash for number {:?}", number); - self.do_get_block_hash(number).map_err(|err| { - error!(target: "sharedbackend", %err, %number, "Failed to send/recv `block_hash`"); - if err.is_possibly_non_archive_node_error() { - error!(target: "sharedbackend", "{NON_ARCHIVE_NODE_WARNING}"); - } - err - }) - } -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - - use crate::fork::JsonBlockCacheDB; - - #[test] - fn can_read_cache() { - let cache_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/storage.json"); - let json = JsonBlockCacheDB::load(cache_path).unwrap(); - assert!(!json.db().accounts.read().is_empty()); - } - - // HACK: workaround for dev dependency not allowed to be optional, but these are - // only used behind the remote feature flag. - use edr_test_utils as _; - use tempfile as _; - - #[cfg(feature = "test-remote")] - mod remote { - use std::{collections::BTreeSet, sync::Arc}; - - use alloy_chains::NamedChain; - use alloy_primitives::{Address, U256}; - use alloy_provider::Provider; - use edr_test_utils::env::get_alchemy_url; - use revm::{ - primitives::{BlockEnv, CfgEnv}, - DatabaseRef, - }; - use tempfile::tempdir; - - use crate::{ - backend::Backend, - fork::{ - provider::get_http_provider, BlockchainDb, BlockchainDbMeta, CreateFork, - SharedBackend, - }, - opts::EvmOpts, - }; - - #[tokio::test(flavor = "multi_thread")] - async fn shared_backend() { - let endpoint = get_alchemy_url(); - - let provider = get_http_provider(&endpoint); - let meta = BlockchainDbMeta { - cfg_env: CfgEnv::default(), - block_env: BlockEnv::default(), - hosts: BTreeSet::from([endpoint]), - }; - - let db = BlockchainDb::new(meta, None); - let backend = SharedBackend::spawn_backend(Arc::new(provider), db.clone(), None).await; - - // some rng contract from etherscan - let address: Address = "63091244180ae240c87d1f528f5f269134cb07b3".parse().unwrap(); - - let idx = U256::from(0u64); - let value = backend.storage_ref(address, idx).unwrap(); - let account = backend.basic_ref(address).unwrap().unwrap(); - - let mem_acc = db.accounts().read().get(&address).unwrap().clone(); - assert_eq!(account.balance, mem_acc.balance); - assert_eq!(account.nonce, mem_acc.nonce); - let slots = db.storage().read().get(&address).unwrap().clone(); - assert_eq!(slots.len(), 1); - assert_eq!(slots.get(&idx).copied().unwrap(), value); - - let num = U256::from(10u64); - let hash = backend.block_hash_ref(num).unwrap(); - let mem_hash = *db.block_hashes().read().get(&num).unwrap(); - assert_eq!(hash, mem_hash); - - let max_slots = 5; - let handle = std::thread::spawn(move || { - for i in 1..max_slots { - let idx = U256::from(i); - let _ = backend.storage_ref(address, idx); - } - }); - handle.join().unwrap(); - let slots = db.storage().read().get(&address).unwrap().clone(); - assert_eq!(slots.len() as u64, max_slots); - } - - #[tokio::test(flavor = "multi_thread")] - async fn can_read_write_cache() { - let endpoint = get_alchemy_url(); - let cache_dir = tempdir().expect("Should create tempdir"); - - let provider = get_http_provider(&endpoint); - - let block_num = provider.get_block_number().await.unwrap(); - - let evm_opts = EvmOpts { - fork_block_number: Some(block_num), - ..EvmOpts::default() - }; - - let (env, _block) = evm_opts.fork_evm_env(&endpoint).await.unwrap(); - - let fork = CreateFork { - rpc_cache_path: Some(cache_dir.path().into()), - url: endpoint, - env: env.clone(), - evm_opts, - }; - - let backend = Backend::spawn(Some(fork.clone())); - - // some rng contract from etherscan - let address: Address = "63091244180ae240c87d1f528f5f269134cb07b3".parse().unwrap(); - - let idx = U256::from(0u64); - let _value = backend.storage_ref(address, idx); - let _account = backend.basic_ref(address); - - // fill some slots - let num_slots = 10u64; - for idx in 1..num_slots { - let _ = backend.storage_ref(address, U256::from(idx)); - } - drop(backend); - - let meta = BlockchainDbMeta { - cfg_env: env.cfg, - block_env: env.block, - hosts: BTreeSet::default(), - }; - - let db = BlockchainDb::new( - meta, - Some( - fork.block_cache_dir(NamedChain::Mainnet, block_num) - .expect("Rpc cache path should be set"), - ), - ); - assert!(db.accounts().read().contains_key(&address)); - assert!(db.storage().read().contains_key(&address)); - assert_eq!( - db.storage().read().get(&address).unwrap().len(), - num_slots as usize - ); - } - } -} diff --git a/crates/foundry/evm/core/src/fork/cache.rs b/crates/foundry/evm/core/src/fork/cache.rs deleted file mode 100644 index 03097276b2..0000000000 --- a/crates/foundry/evm/core/src/fork/cache.rs +++ /dev/null @@ -1,660 +0,0 @@ -//! Cache related abstraction -use std::{ - collections::BTreeSet, - fs, - io::{BufWriter, Write}, - path::PathBuf, - sync::Arc, -}; - -use alloy_primitives::{Address, B256, U256}; -use parking_lot::RwLock; -use revm::{ - primitives::{Account, AccountInfo, AccountStatus, Bytecode, HashMap as Map, KECCAK_EMPTY}, - DatabaseCommit, -}; -use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; -use url::Url; - -use crate::backend::StateSnapshot; - -pub type StorageInfo = Map; - -/// A shareable Block database -#[derive(Clone, Debug)] -pub struct BlockchainDb { - /// Contains all the data - db: Arc, - /// metadata of the current config - meta: Arc>, - /// the cache that can be flushed - cache: Arc, -} - -impl BlockchainDb { - /// Creates a new instance of the [`BlockchainDb`] - /// - /// if a `cache_path` is provided it attempts to load a previously stored - /// [`JsonBlockCacheData`] and will try to use the cached entries it - /// holds. - /// - /// This will return a new and empty [`MemDb`] if - /// - `cache_path` is `None` - /// - the file the `cache_path` points to, does not exist - /// - the file contains malformed data, or if it couldn't be read - /// - the provided `meta` differs from [`BlockchainDbMeta`] that's stored - /// on disk - pub fn new(meta: BlockchainDbMeta, cache_path: Option) -> Self { - Self::new_db(meta, cache_path, false) - } - - /// Creates a new instance of the [`BlockchainDb`] and skips check when - /// comparing meta This is useful for offline-start mode when we don't - /// want to fetch metadata of `block`. - /// - /// if a `cache_path` is provided it attempts to load a previously stored - /// [`JsonBlockCacheData`] and will try to use the cached entries it - /// holds. - /// - /// This will return a new and empty [`MemDb`] if - /// - `cache_path` is `None` - /// - the file the `cache_path` points to, does not exist - /// - the file contains malformed data, or if it couldn't be read - /// - the provided `meta` differs from [`BlockchainDbMeta`] that's stored - /// on disk - pub fn new_skip_check(meta: BlockchainDbMeta, cache_path: Option) -> Self { - Self::new_db(meta, cache_path, true) - } - - fn new_db(meta: BlockchainDbMeta, cache_path: Option, skip_check: bool) -> Self { - trace!(target: "edr_solidity_tests::cache", cache=?cache_path, "initialising blockchain db"); - // read cache and check if metadata matches - let cache = cache_path - .as_ref() - .and_then(|p| { - JsonBlockCacheDB::load(p).ok().filter(|cache| { - if skip_check { - return true; - } - let mut existing = cache.meta().write(); - existing.hosts.extend(meta.hosts.clone()); - if meta != *existing { - warn!(target: "cache", "non-matching block metadata"); - false - } else { - true - } - }) - }) - .unwrap_or_else(|| JsonBlockCacheDB::new(Arc::new(RwLock::new(meta)), cache_path)); - - Self { - db: Arc::clone(cache.db()), - meta: Arc::clone(cache.meta()), - cache: Arc::new(cache), - } - } - - /// Returns the map that holds the account related info - pub fn accounts(&self) -> &RwLock> { - &self.db.accounts - } - - /// Returns the map that holds the storage related info - pub fn storage(&self) -> &RwLock> { - &self.db.storage - } - - /// Returns the map that holds all the block hashes - pub fn block_hashes(&self) -> &RwLock> { - &self.db.block_hashes - } - - /// Returns the [`revm::Env`] related metadata - pub fn meta(&self) -> &Arc> { - &self.meta - } - - /// Returns the inner cache - pub fn cache(&self) -> &Arc { - &self.cache - } - - /// Returns the underlying storage - pub fn db(&self) -> &Arc { - &self.db - } -} - -/// relevant identifying markers in the context of [`BlockchainDb`] -#[derive(Clone, Debug, Eq, Serialize)] -pub struct BlockchainDbMeta { - pub cfg_env: revm::primitives::CfgEnv, - pub block_env: revm::primitives::BlockEnv, - /// all the hosts used to connect to - pub hosts: BTreeSet, -} - -impl BlockchainDbMeta { - /// Creates a new instance - pub fn new(env: revm::primitives::Env, url: String) -> Self { - let host = Url::parse(&url) - .ok() - .and_then(|url| url.host().map(|host| host.to_string())) - .unwrap_or(url); - - BlockchainDbMeta { - cfg_env: env.cfg.clone(), - block_env: env.block, - hosts: BTreeSet::from([host]), - } - } -} - -// ignore hosts to not invalidate the cache when different endpoints are used, -// as it's commonly the case for http vs ws endpoints -impl PartialEq for BlockchainDbMeta { - fn eq(&self, other: &Self) -> bool { - self.cfg_env == other.cfg_env && self.block_env == other.block_env - } -} - -impl<'de> Deserialize<'de> for BlockchainDbMeta { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - /// A backwards compatible representation of - /// [`revm::primitives::CfgEnv`] - /// - /// This prevents deserialization errors of cache files caused by - /// breaking changes to the default [`revm::primitives::CfgEnv`], - /// for example enabling an optional feature. By hand rolling - /// deserialize impl we can prevent cache file issues - struct CfgEnvBackwardsCompat { - inner: revm::primitives::CfgEnv, - } - - impl<'de> Deserialize<'de> for CfgEnvBackwardsCompat { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let mut value = serde_json::Value::deserialize(deserializer)?; - - // we check for breaking changes here - if let Some(obj) = value.as_object_mut() { - let default_value = - serde_json::to_value(revm::primitives::CfgEnv::default()).unwrap(); - for (key, value) in default_value.as_object().unwrap() { - if !obj.contains_key(key) { - obj.insert(key.to_string(), value.clone()); - } - } - } - - let cfg_env: revm::primitives::CfgEnv = - serde_json::from_value(value).map_err(serde::de::Error::custom)?; - Ok(Self { inner: cfg_env }) - } - } - - /// A backwards compatible representation of - /// [`revm::primitives::BlockEnv`] - /// - /// This prevents deserialization errors of cache files caused by - /// breaking changes to the - /// default [`revm::primitives::BlockEnv`], for example enabling an - /// optional feature. By hand rolling deserialize impl we can - /// prevent cache file issues - struct BlockEnvBackwardsCompat { - inner: revm::primitives::BlockEnv, - } - - impl<'de> Deserialize<'de> for BlockEnvBackwardsCompat { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let mut value = serde_json::Value::deserialize(deserializer)?; - - // we check for any missing fields here - if let Some(obj) = value.as_object_mut() { - let default_value = - serde_json::to_value(revm::primitives::BlockEnv::default()).unwrap(); - for (key, value) in default_value.as_object().unwrap() { - if !obj.contains_key(key) { - obj.insert(key.to_string(), value.clone()); - } - } - } - - let cfg_env: revm::primitives::BlockEnv = - serde_json::from_value(value).map_err(serde::de::Error::custom)?; - Ok(Self { inner: cfg_env }) - } - } - - // custom deserialize impl to not break existing cache files - #[derive(Deserialize)] - struct Meta { - cfg_env: CfgEnvBackwardsCompat, - block_env: BlockEnvBackwardsCompat, - /// all the hosts used to connect to - #[serde(alias = "host")] - hosts: Hosts, - } - - #[derive(Deserialize)] - #[serde(untagged)] - enum Hosts { - Multi(BTreeSet), - Single(String), - } - - let Meta { - cfg_env, - block_env, - hosts, - } = Meta::deserialize(deserializer)?; - Ok(Self { - cfg_env: cfg_env.inner, - block_env: block_env.inner, - hosts: match hosts { - Hosts::Multi(hosts) => hosts, - Hosts::Single(host) => BTreeSet::from([host]), - }, - }) - } -} - -/// In Memory cache containing all fetched accounts and storage slots -/// and their values from RPC -#[derive(Debug, Default)] -pub struct MemDb { - /// Account related data - pub accounts: RwLock>, - /// Storage related data - pub storage: RwLock>, - /// All retrieved block hashes - pub block_hashes: RwLock>, -} - -impl MemDb { - /// Clears all data stored in this db - pub fn clear(&self) { - self.accounts.write().clear(); - self.storage.write().clear(); - self.block_hashes.write().clear(); - } - - // Inserts the account, replacing it if it exists already - pub fn do_insert_account(&self, address: Address, account: AccountInfo) { - self.accounts.write().insert(address, account); - } - - /// The implementation of [`DatabaseCommit::commit()`] - pub fn do_commit(&self, changes: Map) { - let mut storage = self.storage.write(); - let mut accounts = self.accounts.write(); - for (add, mut acc) in changes { - if acc.is_empty() || acc.is_selfdestructed() { - accounts.remove(&add); - storage.remove(&add); - } else { - // insert account - if let Some(code_hash) = acc - .info - .code - .as_ref() - .filter(|code| !code.is_empty()) - .map(Bytecode::hash_slow) - { - acc.info.code_hash = code_hash; - } else if acc.info.code_hash.is_zero() { - acc.info.code_hash = KECCAK_EMPTY; - } - accounts.insert(add, acc.info); - - let acc_storage = storage.entry(add).or_default(); - if acc.status.contains(AccountStatus::Created) { - acc_storage.clear(); - } - for (index, value) in acc.storage { - if value.present_value().is_zero() { - acc_storage.remove(&index); - } else { - acc_storage.insert(index, value.present_value()); - } - } - if acc_storage.is_empty() { - storage.remove(&add); - } - } - } - } -} - -impl Clone for MemDb { - fn clone(&self) -> Self { - Self { - storage: RwLock::new(self.storage.read().clone()), - accounts: RwLock::new(self.accounts.read().clone()), - block_hashes: RwLock::new(self.block_hashes.read().clone()), - } - } -} - -impl DatabaseCommit for MemDb { - fn commit(&mut self, changes: Map) { - self.do_commit(changes); - } -} - -/// A [`BlockCacheDB`] that stores the cached content in a json file -#[derive(Debug)] -pub struct JsonBlockCacheDB { - /// Where this cache file is stored. - /// - /// If this is a [None] then caching is disabled - cache_path: Option, - /// Object that's stored in a json file - data: JsonBlockCacheData, -} - -impl JsonBlockCacheDB { - /// Creates a new instance. - fn new(meta: Arc>, cache_path: Option) -> Self { - Self { - cache_path, - data: JsonBlockCacheData { - meta, - data: Arc::new(MemDb::default()), - }, - } - } - - /// Loads the contents of the diskmap file and returns the read object - /// - /// # Errors - /// This will fail if - /// - the `path` does not exist - /// - the format does not match [`JsonBlockCacheData`] - pub fn load(path: impl Into) -> eyre::Result { - let path = path.into(); - trace!(target: "cache", ?path, "reading json cache"); - let contents = std::fs::read_to_string(&path).map_err(|err| { - warn!(?err, ?path, "Failed to read cache file"); - err - })?; - let data = serde_json::from_str(&contents).map_err(|err| { - warn!(target: "cache", ?err, ?path, "Failed to deserialize cache data"); - err - })?; - Ok(Self { - cache_path: Some(path), - data, - }) - } - - /// Returns the [`MemDb`] it holds access to - pub fn db(&self) -> &Arc { - &self.data.data - } - - /// Metadata stored alongside the data - pub fn meta(&self) -> &Arc> { - &self.data.meta - } - - /// Returns `true` if this is a transient cache and nothing will be flushed - pub fn is_transient(&self) -> bool { - self.cache_path.is_none() - } - - /// Flushes the DB to disk if caching is enabled. - #[instrument(level = "warn", skip_all, fields(path = ?self.cache_path))] - pub fn flush(&self) { - let Some(path) = &self.cache_path else { return }; - trace!(target: "cache", "saving json cache"); - - if let Some(parent) = path.parent() { - let _ = fs::create_dir_all(parent); - } - - let file = match fs::File::create(path) { - Ok(file) => file, - Err(e) => return warn!(target: "cache", %e, "Failed to open json cache for writing"), - }; - - let mut writer = BufWriter::new(file); - if let Err(e) = serde_json::to_writer(&mut writer, &self.data) { - return warn!(target: "cache", %e, "Failed to write to json cache"); - } - if let Err(e) = writer.flush() { - return warn!(target: "cache", %e, "Failed to flush to json cache"); - } - - trace!(target: "cache", "saved json cache"); - } -} - -/// The Data the [`JsonBlockCacheDB`] can read and flush -/// -/// This will be deserialized in a JSON object with the keys: -/// `["meta", "accounts", "storage", "block_hashes"]` -#[derive(Debug)] -pub struct JsonBlockCacheData { - pub meta: Arc>, - pub data: Arc, -} - -impl Serialize for JsonBlockCacheData { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut map = serializer.serialize_map(Some(4))?; - - map.serialize_entry("meta", &*self.meta.read())?; - map.serialize_entry("accounts", &*self.data.accounts.read())?; - map.serialize_entry("storage", &*self.data.storage.read())?; - map.serialize_entry("block_hashes", &*self.data.block_hashes.read())?; - - map.end() - } -} - -impl<'de> Deserialize<'de> for JsonBlockCacheData { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - struct Data { - meta: BlockchainDbMeta, - #[serde(flatten)] - data: StateSnapshot, - } - - let Data { - meta, - data: - StateSnapshot { - accounts, - storage, - block_hashes, - }, - } = Data::deserialize(deserializer)?; - - Ok(JsonBlockCacheData { - meta: Arc::new(RwLock::new(meta)), - data: Arc::new(MemDb { - accounts: RwLock::new(accounts), - storage: RwLock::new(storage), - block_hashes: RwLock::new(block_hashes), - }), - }) - } -} - -/// A type that flushes a `JsonBlockCacheDB` on drop -/// -/// This type intentionally does not implement `Clone` since it's intended that -/// there's only once instance that will flush the cache. -#[derive(Debug)] -pub struct FlushJsonBlockCacheDB(pub Arc); - -impl Drop for FlushJsonBlockCacheDB { - fn drop(&mut self) { - trace!(target: "fork::cache", "flushing cache"); - self.0.flush(); - trace!(target: "fork::cache", "flushed cache"); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_deserialize_cache() { - let s = r#"{ - "meta": { - "cfg_env": { - "chain_id": 1337, - "perf_analyse_created_bytecodes": "Analyse", - "limit_contract_code_size": 18446744073709551615, - "memory_limit": 4294967295, - "disable_block_gas_limit": false, - "disable_eip3607": false, - "disable_base_fee": false - }, - "block_env": { - "number": "0xed3ddf", - "coinbase": "0x0000000000000000000000000000000000000000", - "timestamp": "0x6324bc3f", - "difficulty": "0x0", - "basefee": "0x2e5fda223", - "gas_limit": "0x1c9c380", - "prevrandao": "0x0000000000000000000000000000000000000000000000000000000000000000" - }, - "hosts": [ - "eth-mainnet.alchemyapi.io" - ] - }, - "accounts": { - "0xb8ffc3cd6e7cf5a098a1c92f48009765b24088dc": { - "balance": "0x0", - "nonce": 10, - "code_hash": "0x3ac64c95eedf82e5d821696a12daac0e1b22c8ee18a9fd688b00cfaf14550aad", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 1, - "data": [0] - } - } - } - } - }, - "storage": { - "0xa354f35829ae975e850e23e9615b11da1b3dc4de": { - "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564": "0x5553444320795661756c74000000000000000000000000000000000000000000", - "0x10": "0x37fd60ff8346", - "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563": "0xb", - "0x6": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "0x5": "0x36ff5b93162e", - "0x14": "0x29d635a8e000", - "0x11": "0x63224c73", - "0x2": "0x6" - } - }, - "block_hashes": { - "0xed3deb": "0xbf7be3174b261ea3c377b6aba4a1e05d5fae7eee7aab5691087c20cf353e9877", - "0xed3de9": "0xba1c3648e0aee193e7d00dffe4e9a5e420016b4880455641085a4731c1d32eef", - "0xed3de8": "0x61d1491c03a9295fb13395cca18b17b4fa5c64c6b8e56ee9cc0a70c3f6cf9855", - "0xed3de7": "0xb54560b5baeccd18350d56a3bee4035432294dc9d2b7e02f157813e1dee3a0be", - "0xed3dea": "0x816f124480b9661e1631c6ec9ee39350bda79f0cbfc911f925838d88e3d02e4b" - } -}"#; - - let cache: JsonBlockCacheData = serde_json::from_str(s).unwrap(); - assert_eq!(cache.data.accounts.read().len(), 1); - assert_eq!(cache.data.storage.read().len(), 1); - assert_eq!(cache.data.block_hashes.read().len(), 5); - - let _s = serde_json::to_string(&cache).unwrap(); - } - - #[test] - fn can_deserialize_cache_post_4844() { - let s = r#"{ - "meta": { - "cfg_env": { - "chain_id": 1, - "kzg_settings": "Default", - "perf_analyse_created_bytecodes": "Analyse", - "limit_contract_code_size": 18446744073709551615, - "memory_limit": 134217728, - "disable_block_gas_limit": false, - "disable_eip3607": true, - "disable_base_fee": false, - "optimism": false - }, - "block_env": { - "number": "0x11c99bc", - "coinbase": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97", - "timestamp": "0x65627003", - "gas_limit": "0x1c9c380", - "basefee": "0x64288ff1f", - "difficulty": "0xc6b1a299886016dea3865689f8393b9bf4d8f4fe8c0ad25f0058b3569297c057", - "prevrandao": "0xc6b1a299886016dea3865689f8393b9bf4d8f4fe8c0ad25f0058b3569297c057", - "blob_excess_gas_and_price": { - "excess_blob_gas": 0, - "blob_gasprice": 1 - } - }, - "hosts": [ - "eth-mainnet.alchemyapi.io" - ] - }, - "accounts": { - "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97": { - "balance": "0x8e0c373cfcdfd0eb", - "nonce": 128912, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 1, - "data": [0] - } - } - } - } - }, - "storage": {}, - "block_hashes": {} -}"#; - - let cache: JsonBlockCacheData = serde_json::from_str(s).unwrap(); - assert_eq!(cache.data.accounts.read().len(), 1); - - let _s = serde_json::to_string(&cache).unwrap(); - } -} diff --git a/crates/foundry/evm/core/src/fork/database.rs b/crates/foundry/evm/core/src/fork/database.rs index 750ed5ce2c..fc5fc9a619 100644 --- a/crates/foundry/evm/core/src/fork/database.rs +++ b/crates/foundry/evm/core/src/fork/database.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types::BlockId; +use foundry_fork_db::{BlockchainDb, DatabaseError, SharedBackend}; use parking_lot::Mutex; use revm::{ db::{CacheDB, DatabaseRef}, @@ -12,8 +13,7 @@ use revm::{ }; use crate::{ - backend::{DatabaseError, RevertSnapshotAction, StateSnapshot}, - fork::{BlockchainDb, SharedBackend}, + backend::{RevertSnapshotAction, StateSnapshot}, snapshot::Snapshots, }; @@ -289,7 +289,7 @@ mod tests { use revm::primitives::{BlockEnv, CfgEnv}; use super::*; - use crate::fork::{provider::get_http_provider, BlockchainDbMeta}; + use crate::{backend::BlockchainDbMeta, fork::provider::get_http_provider}; /// Demonstrates that `Database::basic` for `ForkedDatabase` will always /// return the `AccountInfo` diff --git a/crates/foundry/evm/core/src/fork/mod.rs b/crates/foundry/evm/core/src/fork/mod.rs index 8c28865f6b..47d34555a9 100644 --- a/crates/foundry/evm/core/src/fork/mod.rs +++ b/crates/foundry/evm/core/src/fork/mod.rs @@ -4,15 +4,9 @@ use revm::primitives::Env; use super::opts::EvmOpts; -mod backend; -pub use backend::{BackendHandler, SharedBackend}; - mod init; -pub use init::environment; - -mod cache; use alloy_chains::Chain; -pub use cache::{BlockchainDb, BlockchainDbMeta, JsonBlockCacheDB, MemDb}; +pub use init::environment; pub mod database; diff --git a/crates/foundry/evm/core/src/fork/multi.rs b/crates/foundry/evm/core/src/fork/multi.rs index 9c7c905052..2ca623db43 100644 --- a/crates/foundry/evm/core/src/fork/multi.rs +++ b/crates/foundry/evm/core/src/fork/multi.rs @@ -15,6 +15,7 @@ use std::{ time::Duration, }; +use foundry_fork_db::{cache::BlockchainDbMeta, BackendHandler, BlockchainDb, SharedBackend}; use futures::{ channel::mpsc::{channel, Receiver, Sender}, stream::{Fuse, Stream}, @@ -23,12 +24,9 @@ use futures::{ }; use revm::primitives::Env; -use crate::fork::{ - provider::{ - runtime_transport::RuntimeTransport, tower::RetryBackoffService, ProviderBuilder, - RetryProvider, - }, - BackendHandler, BlockchainDb, BlockchainDbMeta, CreateFork, SharedBackend, +use super::CreateFork; +use crate::fork::provider::{ + runtime_transport::RuntimeTransport, tower::RetryBackoffService, ProviderBuilder, RetryProvider, }; /// The _unique_ identifier for a specific fork, this could be the name of the diff --git a/crates/foundry/evm/core/src/fork/provider/mod.rs b/crates/foundry/evm/core/src/fork/provider/mod.rs index 525a3e4ab3..9523b02977 100644 --- a/crates/foundry/evm/core/src/fork/provider/mod.rs +++ b/crates/foundry/evm/core/src/fork/provider/mod.rs @@ -13,8 +13,8 @@ use std::{ use alloy_chains::NamedChain; use alloy_provider::{ - fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, SignerFiller}, - network::{AnyNetwork, EthereumSigner}, + fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller}, + network::{AnyNetwork, EthereumWallet}, Identity, ProviderBuilder as AlloyProviderBuilder, RootProvider, }; use alloy_rpc_client::ClientBuilder; @@ -33,7 +33,7 @@ pub type RetryProvider = RootProvider = FillProvider< JoinFill< JoinFill, NonceFiller>, ChainIdFiller>, - SignerFiller, + WalletFiller, >, RootProvider, N>, RetryBackoffService, @@ -283,7 +283,7 @@ impl ProviderBuilder { } /// Constructs the `RetryProvider` with a signer - pub fn build_with_signer(self, signer: EthereumSigner) -> Result { + pub fn build_with_signer(self, wallet: EthereumWallet) -> Result { let ProviderBuilder { url, chain: _, @@ -317,7 +317,7 @@ impl ProviderBuilder { let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() .with_recommended_fillers() - .signer(signer) + .wallet(wallet) .on_provider(RootProvider::new(client)); Ok(provider) diff --git a/crates/foundry/evm/core/src/fork/provider/runtime_transport.rs b/crates/foundry/evm/core/src/fork/provider/runtime_transport.rs index 10c40600b4..41266a9426 100644 --- a/crates/foundry/evm/core/src/fork/provider/runtime_transport.rs +++ b/crates/foundry/evm/core/src/fork/provider/runtime_transport.rs @@ -5,7 +5,7 @@ use std::{path::PathBuf, str::FromStr, sync::Arc}; use alloy_json_rpc::{RequestPacket, ResponsePacket}; use alloy_pubsub::{PubSubConnect, PubSubFrontend}; -use alloy_rpc_types_engine::{Claims, JwtSecret}; +use alloy_rpc_types::engine::{Claims, JwtSecret}; use alloy_transport::{ Authorization, BoxTransport, TransportError, TransportErrorKind, TransportFut, }; diff --git a/crates/foundry/evm/evm/src/executors/mod.rs b/crates/foundry/evm/evm/src/executors/mod.rs index 5a1107d18c..5b5a7ff9d0 100644 --- a/crates/foundry/evm/evm/src/executors/mod.rs +++ b/crates/foundry/evm/evm/src/executors/mod.rs @@ -14,7 +14,7 @@ use alloy_json_abi::Function; use alloy_primitives::{Address, Bytes, Log, U256}; use alloy_sol_types::{sol, SolCall}; use foundry_evm_core::{ - backend::{Backend, CowBackend, DatabaseError, DatabaseExt, DatabaseResult}, + backend::{Backend, BackendError, BackendResult, CowBackend, DatabaseExt}, constants::{ CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, DEFAULT_CREATE2_DEPLOYER_CODE, }, @@ -117,7 +117,7 @@ impl Executor { let create2_deployer_account = self .backend .basic_ref(DEFAULT_CREATE2_DEPLOYER)? - .ok_or_else(|| DatabaseError::MissingAccount(DEFAULT_CREATE2_DEPLOYER))?; + .ok_or_else(|| BackendError::MissingAccount(DEFAULT_CREATE2_DEPLOYER))?; // if the deployer is not currently deployed, deploy the default one if create2_deployer_account @@ -146,7 +146,7 @@ impl Executor { } /// Set the balance of an account. - pub fn set_balance(&mut self, address: Address, amount: U256) -> DatabaseResult<&mut Self> { + pub fn set_balance(&mut self, address: Address, amount: U256) -> BackendResult<&mut Self> { trace!(?address, ?amount, "setting account balance"); let mut account = self.backend.basic_ref(address)?.unwrap_or_default(); account.balance = amount; @@ -156,7 +156,7 @@ impl Executor { } /// Gets the balance of an account - pub fn get_balance(&self, address: Address) -> DatabaseResult { + pub fn get_balance(&self, address: Address) -> BackendResult { Ok(self .backend .basic_ref(address)? @@ -165,7 +165,7 @@ impl Executor { } /// Set the nonce of an account. - pub fn set_nonce(&mut self, address: Address, nonce: u64) -> DatabaseResult<&mut Self> { + pub fn set_nonce(&mut self, address: Address, nonce: u64) -> BackendResult<&mut Self> { let mut account = self.backend.basic_ref(address)?.unwrap_or_default(); account.nonce = nonce; @@ -174,7 +174,7 @@ impl Executor { } /// Gets the nonce of an account - pub fn get_nonce(&self, address: Address) -> DatabaseResult { + pub fn get_nonce(&self, address: Address) -> BackendResult { Ok(self .backend .basic_ref(address)? @@ -183,7 +183,7 @@ impl Executor { } /// Returns true if account has no code. - pub fn is_empty_code(&self, address: Address) -> DatabaseResult { + pub fn is_empty_code(&self, address: Address) -> BackendResult { Ok(self .backend .basic_ref(address)? @@ -511,7 +511,7 @@ impl Executor { reverted: bool, state_changeset: Cow<'_, StateChangeset>, should_fail: bool, - ) -> Result { + ) -> Result { if self.backend.has_snapshot_failure() { // a failure occurred in a reverted snapshot, which is considered a failed test return Ok(should_fail);