Compare commits

...

9 Commits

Author SHA1 Message Date
Omar Abdulla 2a9d497827 Update the initial balance 2025-07-24 11:40:33 +03:00
Omar Abdulla 4763d763f6 Revert "Add extra comments"
This reverts commit bd4de2c83d.
2025-07-24 10:38:23 +03:00
Omar Abdulla bd4de2c83d Add extra comments 2025-07-24 09:48:18 +03:00
Omar Abdulla 3c70e0ac76 Revert "Bump commit hash of polkadot SDK"
This reverts commit 8aaa69780e.
2025-07-24 09:39:08 +03:00
Omar Abdulla 995ef80940 Revert "Change how the cache key is computed"
This reverts commit 75afdd9cfd.
2025-07-24 09:39:00 +03:00
Omar Abdulla 75afdd9cfd Change how the cache key is computed 2025-07-24 08:31:50 +03:00
Omar Abdulla 8aaa69780e Bump commit hash of polkadot SDK 2025-07-24 08:19:05 +03:00
Omar Abdulla d7795fff9d Pre-seed accounts with more ETH.
This commit fixes and solves some issues around how much ETH we seed an
account with in genesis. Currently, any account that the node has keys
to sign for will be seeded with u128::MAX WEI in genesis. This also
includes the default signer account.
2025-07-24 06:58:46 +03:00
Omar 6f4aa731ab Handle exceptions (#54)
* Add support for wrapper types

* Move `FilesWithExtensionIterator` to `core::common`

* Remove unneeded use of two `HashMap`s

* Make metadata structs more typed

* Impl new_from for wrapper types

* Implement the new input handling logic

* Fix edge-case in input handling

* Ignore macro doc comment tests

* Correct comment

* Fix edge-case in deployment order

* Handle calldata better

* Allow for the use of function signatures

* Add support for exceptions

* Cached nonce allocator

* Fix tests

* Add support for address replacement

* Cleanup implementation

* Cleanup mutability

* Wire up address replacement with rest of code

* Implement caller replacement

* Switch to callframe trace for exceptions

* Add a way to skip tests if they don't match the target

* Handle values from the metadata files

* Remove address replacement

* Correct the arguments

* Remove empty impl

* Remove address replacement

* Correct the arguments

* Remove empty impl

* Fix size_requirement underflow

* Add support for wildcards in exceptions

* Fix calldata construction of single calldata

* Better handling for length in equivalency checks

* Make initial balance a constant

* Fix size_requirement underflow

* Add support for wildcards in exceptions

* Fix calldata construction of single calldata

* Better handling for length in equivalency checks

* Fix tests
2025-07-24 03:45:53 +00:00
16 changed files with 765 additions and 296 deletions
Generated
+75 -69
View File
@@ -67,9 +67,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "alloy" name = "alloy"
version = "1.0.20" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae58d888221eecf621595e2096836ce7cfc37be06bfa39d7f64aa6a3ea4c9e5b" checksum = "8ad4eb51e7845257b70c51b38ef8d842d5e5e93196701fcbd757577971a043c6"
dependencies = [ dependencies = [
"alloy-consensus", "alloy-consensus",
"alloy-contract", "alloy-contract",
@@ -102,15 +102,16 @@ dependencies = [
[[package]] [[package]]
name = "alloy-consensus" name = "alloy-consensus"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad451f9a70c341d951bca4e811d74dbe1e193897acd17e9dbac1353698cc430b" checksum = "ca3b746060277f3d7f9c36903bb39b593a741cb7afcb0044164c28f0e9b673f0"
dependencies = [ dependencies = [
"alloy-eips", "alloy-eips",
"alloy-primitives", "alloy-primitives",
"alloy-rlp", "alloy-rlp",
"alloy-serde", "alloy-serde",
"alloy-trie", "alloy-trie",
"alloy-tx-macros",
"auto_impl", "auto_impl",
"c-kzg", "c-kzg",
"derive_more 2.0.1", "derive_more 2.0.1",
@@ -126,9 +127,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-consensus-any" name = "alloy-consensus-any"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "142daffb15d5be1a2b20d2cd540edbcef03037b55d4ff69dc06beb4d06286dba" checksum = "bf98679329fa708fa809ea596db6d974da892b068ad45e48ac1956f582edf946"
dependencies = [ dependencies = [
"alloy-consensus", "alloy-consensus",
"alloy-eips", "alloy-eips",
@@ -140,9 +141,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-contract" name = "alloy-contract"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebf25443920ecb9728cb087fe4dc04a0b290bd6ac85638c58fe94aba70f1a44e" checksum = "a10e47f5305ea08c37b1772086c1573e9a0a257227143996841172d37d3831bb"
dependencies = [ dependencies = [
"alloy-consensus", "alloy-consensus",
"alloy-dyn-abi", "alloy-dyn-abi",
@@ -157,6 +158,7 @@ dependencies = [
"alloy-transport", "alloy-transport",
"futures", "futures",
"futures-util", "futures-util",
"serde_json",
"thiserror 2.0.12", "thiserror 2.0.12",
] ]
@@ -227,9 +229,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-eips" name = "alloy-eips"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3056872f6da48046913e76edb5ddced272861f6032f09461aea1a2497be5ae5d" checksum = "f562a81278a3ed83290e68361f2d1c75d018ae3b8589a314faf9303883e18ec9"
dependencies = [ dependencies = [
"alloy-eip2124", "alloy-eip2124",
"alloy-eip2930", "alloy-eip2930",
@@ -247,15 +249,16 @@ dependencies = [
[[package]] [[package]]
name = "alloy-genesis" name = "alloy-genesis"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c98fb40f07997529235cc474de814cd7bd9de561e101716289095696c0e4639d" checksum = "dc41384e9ab8c9b2fb387c52774d9d432656a28edcda1c2d4083e96051524518"
dependencies = [ dependencies = [
"alloy-eips", "alloy-eips",
"alloy-primitives", "alloy-primitives",
"alloy-serde", "alloy-serde",
"alloy-trie", "alloy-trie",
"serde", "serde",
"serde_with",
] ]
[[package]] [[package]]
@@ -272,12 +275,13 @@ dependencies = [
[[package]] [[package]]
name = "alloy-json-rpc" name = "alloy-json-rpc"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc08b31ebf9273839bd9a01f9333cbb7a3abb4e820c312ade349dd18bdc79581" checksum = "12c454fcfcd5d26ed3b8cae5933cbee9da5f0b05df19b46d4bd4446d1f082565"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"alloy-sol-types", "alloy-sol-types",
"http",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 2.0.12", "thiserror 2.0.12",
@@ -286,9 +290,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-network" name = "alloy-network"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed117b08f0cc190312bf0c38c34cf4f0dabfb4ea8f330071c587cd7160a88cb2" checksum = "42d6d39eabe5c7b3d8f23ac47b0b683b99faa4359797114636c66e0743103d05"
dependencies = [ dependencies = [
"alloy-consensus", "alloy-consensus",
"alloy-consensus-any", "alloy-consensus-any",
@@ -312,9 +316,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-network-primitives" name = "alloy-network-primitives"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7162ff7be8649c0c391f4e248d1273e85c62076703a1f3ec7daf76b283d886d" checksum = "3704fa8b7ba9ba3f378d99b3d628c8bc8c2fc431b709947930f154e22a8368b6"
dependencies = [ dependencies = [
"alloy-consensus", "alloy-consensus",
"alloy-eips", "alloy-eips",
@@ -342,7 +346,7 @@ dependencies = [
"keccak-asm", "keccak-asm",
"paste", "paste",
"proptest", "proptest",
"rand 0.9.1", "rand 0.9.2",
"ruint", "ruint",
"rustc-hash", "rustc-hash",
"serde", "serde",
@@ -352,9 +356,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-provider" name = "alloy-provider"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d84eba1fd8b6fe8b02f2acd5dd7033d0f179e304bd722d11e817db570d1fa6c4" checksum = "08800e8cbe70c19e2eb7cf3d7ff4b28bdd9b3933f8e1c8136c7d910617ba03bf"
dependencies = [ dependencies = [
"alloy-chains", "alloy-chains",
"alloy-consensus", "alloy-consensus",
@@ -380,6 +384,7 @@ dependencies = [
"either", "either",
"futures", "futures",
"futures-utils-wasm", "futures-utils-wasm",
"http",
"lru", "lru",
"parking_lot", "parking_lot",
"pin-project", "pin-project",
@@ -395,9 +400,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-pubsub" name = "alloy-pubsub"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8550f7306e0230fc835eb2ff4af0a96362db4b6fc3f25767d161e0ad0ac765bf" checksum = "ae68457a2c2ead6bd7d7acb5bf5f1623324b1962d4f8e7b0250657a3c3ab0a0b"
dependencies = [ dependencies = [
"alloy-json-rpc", "alloy-json-rpc",
"alloy-primitives", "alloy-primitives",
@@ -438,9 +443,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-rpc-client" name = "alloy-rpc-client"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "518a699422a3eab800f3dac2130d8f2edba8e4fff267b27a9c7dc6a2b0d313ee" checksum = "162301b5a57d4d8f000bf30f4dcb82f9f468f3e5e846eeb8598dd39e7886932c"
dependencies = [ dependencies = [
"alloy-json-rpc", "alloy-json-rpc",
"alloy-primitives", "alloy-primitives",
@@ -448,7 +453,6 @@ dependencies = [
"alloy-transport", "alloy-transport",
"alloy-transport-http", "alloy-transport-http",
"alloy-transport-ipc", "alloy-transport-ipc",
"async-stream",
"futures", "futures",
"pin-project", "pin-project",
"reqwest", "reqwest",
@@ -458,16 +462,15 @@ dependencies = [
"tokio-stream", "tokio-stream",
"tower", "tower",
"tracing", "tracing",
"tracing-futures",
"url", "url",
"wasmtimer", "wasmtimer",
] ]
[[package]] [[package]]
name = "alloy-rpc-types" name = "alloy-rpc-types"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c000cab4ec26a4b3e29d144e999e1c539c2fa0abed871bf90311eb3466187ca8" checksum = "6cd8ca94ae7e2b32cc3895d9981f3772aab0b4756aa60e9ed0bcfee50f0e1328"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"alloy-rpc-types-eth", "alloy-rpc-types-eth",
@@ -478,9 +481,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-rpc-types-any" name = "alloy-rpc-types-any"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "508b2fbe66d952089aa694e53802327798806498cd29ff88c75135770ecaabfc" checksum = "076b47e834b367d8618c52dd0a0d6a711ddf66154636df394805300af4923b8a"
dependencies = [ dependencies = [
"alloy-consensus-any", "alloy-consensus-any",
"alloy-rpc-types-eth", "alloy-rpc-types-eth",
@@ -489,9 +492,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-rpc-types-debug" name = "alloy-rpc-types-debug"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c832f2e851801093928dbb4b7bd83cd22270faf76b2e080646b806a285c8757" checksum = "94a2a86ad7b7d718c15e79d0779bd255561b6b22968dc5ed2e7c0fbc43bb55fe"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"serde", "serde",
@@ -499,9 +502,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-rpc-types-eth" name = "alloy-rpc-types-eth"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcaf7dff0fdd756a714d58014f4f8354a1706ebf9fa2cf73431e0aeec3c9431e" checksum = "2c2f847e635ec0be819d06e2ada4bcc4e4204026a83c4bfd78ae8d550e027ae7"
dependencies = [ dependencies = [
"alloy-consensus", "alloy-consensus",
"alloy-consensus-any", "alloy-consensus-any",
@@ -514,14 +517,15 @@ dependencies = [
"itertools 0.14.0", "itertools 0.14.0",
"serde", "serde",
"serde_json", "serde_json",
"serde_with",
"thiserror 2.0.12", "thiserror 2.0.12",
] ]
[[package]] [[package]]
name = "alloy-rpc-types-trace" name = "alloy-rpc-types-trace"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e3507a04e868dd83219ad3cd6a8c58aefccb64d33f426b3934423a206343e84" checksum = "6fc58180302a94c934d455eeedb3ecb99cdc93da1dbddcdbbdb79dd6fe618b2a"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"alloy-rpc-types-eth", "alloy-rpc-types-eth",
@@ -533,9 +537,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-serde" name = "alloy-serde"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "730e8f2edf2fc224cabd1c25d090e1655fa6137b2e409f92e5eec735903f1507" checksum = "ae699248d02ade9db493bbdae61822277dc14ae0f82a5a4153203b60e34422a6"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"serde", "serde",
@@ -544,9 +548,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-signer" name = "alloy-signer"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b0d2428445ec13edc711909e023d7779618504c4800be055a5b940025dbafe3" checksum = "3cf7d793c813515e2b627b19a15693960b3ed06670f9f66759396d06ebe5747b"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"async-trait", "async-trait",
@@ -559,9 +563,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-signer-local" name = "alloy-signer-local"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14fe6fedb7fe6e0dfae47fe020684f1d8e063274ef14bca387ddb7a6efa8ec1" checksum = "51a424bc5a11df0d898ce0fd15906b88ebe2a6e4f17a514b51bc93946bb756bd"
dependencies = [ dependencies = [
"alloy-consensus", "alloy-consensus",
"alloy-network", "alloy-network",
@@ -648,9 +652,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-transport" name = "alloy-transport"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a712bdfeff42401a7dd9518f72f617574c36226a9b5414537fedc34350b73bf9" checksum = "4f317d20f047b3de4d9728c556e2e9a92c9a507702d2016424cd8be13a74ca5e"
dependencies = [ dependencies = [
"alloy-json-rpc", "alloy-json-rpc",
"alloy-primitives", "alloy-primitives",
@@ -671,9 +675,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-transport-http" name = "alloy-transport-http"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ea5a76d7f2572174a382aedf36875bedf60bcc41116c9f031cf08040703a2dc" checksum = "ff084ac7b1f318c87b579d221f11b748341d68b9ddaa4ffca5e62ed2b8cfefb4"
dependencies = [ dependencies = [
"alloy-json-rpc", "alloy-json-rpc",
"alloy-transport", "alloy-transport",
@@ -686,9 +690,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-transport-ipc" name = "alloy-transport-ipc"
version = "1.0.9" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "606af17a7e064d219746f6d2625676122c79d78bf73dfe746d6db9ecd7dbcb85" checksum = "edb099cdad8ed2e6a80811cdf9bbf715ebf4e34c981b4a6e2d1f9daacbf8b218"
dependencies = [ dependencies = [
"alloy-json-rpc", "alloy-json-rpc",
"alloy-pubsub", "alloy-pubsub",
@@ -706,9 +710,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-trie" name = "alloy-trie"
version = "0.8.1" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "983d99aa81f586cef9dae38443245e585840fcf0fc58b09aee0b1f27aed1d500" checksum = "bada1fc392a33665de0dc50d401a3701b62583c655e3522a323490a5da016962"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"alloy-rlp", "alloy-rlp",
@@ -720,6 +724,19 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "alloy-tx-macros"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1154c8187a5ff985c95a8b2daa2fedcf778b17d7668e5e50e556c4ff9c881154"
dependencies = [
"alloy-primitives",
"darling",
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]] [[package]]
name = "android-tzdata" name = "android-tzdata"
version = "0.1.1" version = "0.1.1"
@@ -3268,13 +3285,14 @@ dependencies = [
[[package]] [[package]]
name = "nybbles" name = "nybbles"
version = "0.3.4" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" checksum = "675b3a54e5b12af997abc8b6638b0aee51a28caedab70d4967e0d5db3a3f1d06"
dependencies = [ dependencies = [
"alloy-rlp", "alloy-rlp",
"const-hex", "cfg-if",
"proptest", "proptest",
"ruint",
"serde", "serde",
"smallvec", "smallvec",
] ]
@@ -3719,9 +3737,9 @@ dependencies = [
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.9.1" version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [ dependencies = [
"rand_chacha 0.9.0", "rand_chacha 0.9.0",
"rand_core 0.9.3", "rand_core 0.9.3",
@@ -4113,7 +4131,7 @@ dependencies = [
"primitive-types 0.12.2", "primitive-types 0.12.2",
"proptest", "proptest",
"rand 0.8.5", "rand 0.8.5",
"rand 0.9.1", "rand 0.9.2",
"rlp", "rlp",
"ruint-macro", "ruint-macro",
"serde", "serde",
@@ -5489,18 +5507,6 @@ dependencies = [
"valuable", "valuable",
] ]
[[package]]
name = "tracing-futures"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
dependencies = [
"futures",
"futures-task",
"pin-project",
"tracing",
]
[[package]] [[package]]
name = "tracing-log" name = "tracing-log"
version = "0.2.0" version = "0.2.0"
+2 -1
View File
@@ -59,7 +59,7 @@ revive-common = { git = "https://github.com/paritytech/revive", rev = "3389865af
revive-differential = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" } revive-differential = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" }
[workspace.dependencies.alloy] [workspace.dependencies.alloy]
version = "1.0" version = "1.0.22"
default-features = false default-features = false
features = [ features = [
"json-abi", "json-abi",
@@ -73,6 +73,7 @@ features = [
"network", "network",
"serde", "serde",
"rpc-types-eth", "rpc-types-eth",
"genesis",
] ]
[profile.bench] [profile.bench]
+6
View File
@@ -73,6 +73,12 @@ pub struct Arguments {
)] )]
pub account: String, pub account: String,
/// This argument controls which private keys the nodes should have access to and be added to
/// its wallet signers. With a value of N, private keys (0, N] will be added to the signer set
/// of the node.
#[arg(long = "private-keys-count", default_value_t = 30)]
pub private_keys_to_add: usize,
/// The differential testing leader node implementation. /// The differential testing leader node implementation.
#[arg(short, long = "leader", default_value = "geth")] #[arg(short, long = "leader", default_value = "geth")]
pub leader: TestingPlatform, pub leader: TestingPlatform,
+243 -35
View File
@@ -6,7 +6,10 @@ use std::marker::PhantomData;
use alloy::json_abi::JsonAbi; use alloy::json_abi::JsonAbi;
use alloy::network::{Ethereum, TransactionBuilder}; use alloy::network::{Ethereum, TransactionBuilder};
use alloy::rpc::types::TransactionReceipt; use alloy::rpc::types::TransactionReceipt;
use alloy::rpc::types::trace::geth::GethTrace; use alloy::rpc::types::trace::geth::{
CallFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingOptions, GethTrace,
PreStateConfig,
};
use alloy::{ use alloy::{
primitives::Address, primitives::Address,
rpc::types::{ rpc::types::{
@@ -19,9 +22,10 @@ use indexmap::IndexMap;
use revive_dt_compiler::{Compiler, SolidityCompiler}; use revive_dt_compiler::{Compiler, SolidityCompiler};
use revive_dt_config::Arguments; use revive_dt_config::Arguments;
use revive_dt_format::case::CaseIdx; use revive_dt_format::case::CaseIdx;
use revive_dt_format::input::Method; use revive_dt_format::input::{Calldata, Expected, ExpectedOutput, Method};
use revive_dt_format::metadata::{ContractInstance, ContractPathAndIdentifier}; use revive_dt_format::metadata::{ContractInstance, ContractPathAndIdentifier};
use revive_dt_format::{input::Input, metadata::Metadata, mode::SolcMode}; use revive_dt_format::{input::Input, metadata::Metadata, mode::SolcMode};
use revive_dt_node::Node;
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
use revive_dt_report::reporter::{CompilationTask, Report, Span}; use revive_dt_report::reporter::{CompilationTask, Report, Span};
use revive_solc_json_interface::SolcStandardJsonOutput; use revive_solc_json_interface::SolcStandardJsonOutput;
@@ -145,7 +149,10 @@ where
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> { ) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
let deployment_receipts = let deployment_receipts =
self.handle_contract_deployment(metadata, case_idx, input, node)?; self.handle_contract_deployment(metadata, case_idx, input, node)?;
self.handle_input_execution(case_idx, input, deployment_receipts, node) let execution_receipt =
self.handle_input_execution(case_idx, input, deployment_receipts, node)?;
self.handle_input_expectations(case_idx, input, &execution_receipt, node)?;
self.handle_input_diff(case_idx, execution_receipt, node)
} }
/// Handles the contract deployment for a given input performing it if it needs to be performed. /// Handles the contract deployment for a given input performing it if it needs to be performed.
@@ -243,6 +250,12 @@ where
let tx = { let tx = {
let tx = TransactionRequest::default().from(input.caller); let tx = TransactionRequest::default().from(input.caller);
let tx = match input.value {
Some(ref value) if deploy_with_constructor_arguments => {
tx.value(value.into_inner())
}
_ => tx,
};
TransactionBuilder::<Ethereum>::with_deploy_code(tx, code) TransactionBuilder::<Ethereum>::with_deploy_code(tx, code)
}; };
@@ -308,18 +321,15 @@ where
&mut self, &mut self,
case_idx: CaseIdx, case_idx: CaseIdx,
input: &Input, input: &Input,
deployment_receipts: HashMap<ContractInstance, TransactionReceipt>, mut deployment_receipts: HashMap<ContractInstance, TransactionReceipt>,
node: &T::Blockchain, node: &T::Blockchain,
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> { ) -> anyhow::Result<TransactionReceipt> {
tracing::trace!("Calling execute_input for input: {input:?}"); match input.method {
let receipt = match input.method {
// This input was already executed when `handle_input` was called. We just need to // This input was already executed when `handle_input` was called. We just need to
// lookup the transaction receipt in this case and continue on. // lookup the transaction receipt in this case and continue on.
Method::Deployer => deployment_receipts Method::Deployer => deployment_receipts
.get(&input.instance) .remove(&input.instance)
.context("Failed to find deployment receipt")? .context("Failed to find deployment receipt"),
.clone(),
Method::Fallback | Method::FunctionName(_) => { Method::Fallback | Method::FunctionName(_) => {
let tx = match input let tx = match input
.legacy_transaction(self.deployed_contracts.entry(case_idx).or_default(), node) .legacy_transaction(self.deployed_contracts.entry(case_idx).or_default(), node)
@@ -337,35 +347,224 @@ where
tracing::trace!("Executing transaction for input: {input:?}"); tracing::trace!("Executing transaction for input: {input:?}");
match node.execute_transaction(tx) { match node.execute_transaction(tx) {
Ok(receipt) => receipt, Ok(receipt) => Ok(receipt),
Err(err) => { Err(err) => {
tracing::error!( tracing::error!(
"Failed to execute transaction when executing the contract: {}, {:?}", "Failed to execute transaction when executing the contract: {}, {:?}",
&*input.instance, &*input.instance,
err err
); );
return Err(err); Err(err)
} }
} }
} }
}
}
fn handle_input_expectations(
&mut self,
case_idx: CaseIdx,
input: &Input,
execution_receipt: &TransactionReceipt,
node: &T::Blockchain,
) -> anyhow::Result<()> {
let span = tracing::info_span!("Handling input expectations");
let _guard = span.enter();
// Resolving the `input.expected` into a series of expectations that we can then assert on.
let mut expectations = match input {
Input {
expected: Some(Expected::Calldata(calldata)),
..
} => vec![ExpectedOutput::new().with_calldata(calldata.clone())],
Input {
expected: Some(Expected::Expected(expected)),
..
} => vec![expected.clone()],
Input {
expected: Some(Expected::ExpectedMany(expected)),
..
} => expected.clone(),
Input { expected: None, .. } => vec![ExpectedOutput::new().with_success()],
}; };
tracing::trace!( // This is a bit of a special case and we have to support it separately on it's own. If it's
"Transaction receipt for executed contract: {} - {:?}", // a call to the deployer method, then the tests will assert that it "returns" the address
&*input.instance, // of the contract. Deployments do not return the address of the contract but the runtime
receipt, // code of the contracts. Therefore, this assertion would always fail. So, we replace it
// with an assertion of "check if it succeeded"
if let Method::Deployer = &input.method {
for expectation in expectations.iter_mut() {
expectation.return_data = None;
}
}
// Note: we need to do assertions and checks on the output of the last call and this isn't
// available in the receipt. The only way to get this information is through tracing on the
// node.
let tracing_result = node
.trace_transaction(
execution_receipt,
GethDebugTracingOptions {
tracer: Some(GethDebugTracerType::BuiltInTracer(
GethDebugBuiltInTracerType::CallTracer,
)),
..Default::default()
},
)?
.try_into_call_frame()
.expect("Impossible - we requested a callframe trace so we must get it back");
for expectation in expectations.iter() {
self.handle_input_expectation_item(
case_idx,
execution_receipt,
node,
expectation,
&tracing_result,
)?;
}
Ok(())
}
fn handle_input_expectation_item(
&mut self,
case_idx: CaseIdx,
execution_receipt: &TransactionReceipt,
node: &T::Blockchain,
expectation: &ExpectedOutput,
tracing_result: &CallFrame,
) -> anyhow::Result<()> {
// TODO: We want to respect the compiler version filter on the expected output but would
// require some changes to the interfaces of the compiler and such. So, we add it later.
// Additionally, what happens if the compiler filter doesn't match? Do we consider that the
// transaction should succeed? Do we just ignore the expectation?
let deployed_contracts = self.deployed_contracts.entry(case_idx).or_default();
let chain_state_provider = node;
// Handling the receipt state assertion.
let expected = !expectation.exception;
let actual = execution_receipt.status();
if actual != expected {
tracing::error!(expected, actual, "Transaction status assertion failed",);
anyhow::bail!(
"Transaction status assertion failed - Expected {expected} but got {actual}",
); );
}
let trace = node.trace_transaction(receipt.clone())?; // Handling the calldata assertion
tracing::trace!( if let Some(ref expected_calldata) = expectation.return_data {
"Trace result for contract: {} - {:?}", let expected = expected_calldata;
&*input.instance, let actual = &tracing_result.output.as_ref().unwrap_or_default();
trace if !expected.is_equivalent(actual, deployed_contracts, chain_state_provider)? {
tracing::error!(
?execution_receipt,
?expected,
%actual,
"Calldata assertion failed"
); );
anyhow::bail!("Calldata assertion failed - Expected {expected:?} but got {actual}",);
}
}
let diff = node.state_diff(receipt.clone())?; // Handling the events assertion
if let Some(ref expected_events) = expectation.events {
// Handling the events length assertion.
let expected = expected_events.len();
let actual = execution_receipt.logs().len();
if actual != expected {
tracing::error!(expected, actual, "Event count assertion failed",);
anyhow::bail!(
"Event count assertion failed - Expected {expected} but got {actual}",
);
}
Ok((receipt, trace, diff)) // Handling the events assertion.
for (expected_event, actual_event) in
expected_events.iter().zip(execution_receipt.logs())
{
// Handling the emitter assertion.
if let Some(expected_address) = expected_event.address {
let expected = expected_address;
let actual = actual_event.address();
if actual != expected {
tracing::error!(
%expected,
%actual,
"Event emitter assertion failed",
);
anyhow::bail!(
"Event emitter assertion failed - Expected {expected} but got {actual}",
);
}
}
// Handling the topics assertion.
for (expected, actual) in expected_event
.topics
.as_slice()
.iter()
.zip(actual_event.topics())
{
let expected = Calldata::Compound(vec![expected.clone()]);
if !expected.is_equivalent(
&actual.0,
deployed_contracts,
chain_state_provider,
)? {
tracing::error!(
?execution_receipt,
?expected,
?actual,
"Event topics assertion failed",
);
anyhow::bail!(
"Event topics assertion failed - Expected {expected:?} but got {actual:?}",
);
}
}
// Handling the values assertion.
let expected = &expected_event.values;
let actual = &actual_event.data().data;
if !expected.is_equivalent(&actual.0, deployed_contracts, chain_state_provider)? {
tracing::error!(
?execution_receipt,
?expected,
?actual,
"Event value assertion failed",
);
anyhow::bail!(
"Event value assertion failed - Expected {expected:?} but got {actual:?}",
);
}
}
}
Ok(())
}
fn handle_input_diff(
&mut self,
_: CaseIdx,
execution_receipt: TransactionReceipt,
node: &T::Blockchain,
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
let span = tracing::info_span!("Handling input diff");
let _guard = span.enter();
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
diff_mode: Some(true),
disable_code: None,
disable_storage: None,
});
let trace = node.trace_transaction(&execution_receipt, trace_options)?;
let diff = node.state_diff(&execution_receipt)?;
Ok((execution_receipt, trace, diff))
} }
} }
@@ -450,6 +649,22 @@ where
let tracing_span = tracing::info_span!("Handling metadata file"); let tracing_span = tracing::info_span!("Handling metadata file");
let _guard = tracing_span.enter(); let _guard = tracing_span.enter();
// We only execute this input if it's valid for the leader and the follower. Otherwise, we
// skip it with a warning.
if !self
.leader_node
.matches_target(self.metadata.targets.as_deref())
|| !self
.follower_node
.matches_target(self.metadata.targets.as_deref())
{
tracing::warn!(
targets = ?self.metadata.targets,
"Either the leader or follower node do not support the targets of the file"
);
return execution_result;
}
for mode in self.metadata.solc_modes() { for mode in self.metadata.solc_modes() {
let tracing_span = tracing::info_span!("With solc mode", solc_mode = ?mode); let tracing_span = tracing::info_span!("With solc mode", solc_mode = ?mode);
let _guard = tracing_span.enter(); let _guard = tracing_span.enter();
@@ -493,6 +708,7 @@ where
// For cases if one of the inputs fail then we move on to the next case and we do NOT // For cases if one of the inputs fail then we move on to the next case and we do NOT
// bail out of the whole thing. // bail out of the whole thing.
'case_loop: for (case_idx, case) in self.metadata.cases.iter().enumerate() { 'case_loop: for (case_idx, case) in self.metadata.cases.iter().enumerate() {
let tracing_span = tracing::info_span!( let tracing_span = tracing::info_span!(
"Handling case", "Handling case",
@@ -505,7 +721,7 @@ where
// For inputs if one of the inputs fail we move on to the next case (we do not move // For inputs if one of the inputs fail we move on to the next case (we do not move
// on to the next input as it doesn't make sense. It depends on the previous one). // on to the next input as it doesn't make sense. It depends on the previous one).
for (input_idx, input) in case.inputs.iter().enumerate() { for (input_idx, input) in case.inputs_iterator().enumerate() {
let tracing_span = tracing::info_span!("Handling input", input_idx); let tracing_span = tracing::info_span!("Handling input", input_idx);
let _guard = tracing_span.enter(); let _guard = tracing_span.enter();
@@ -513,7 +729,7 @@ where
tracing::info_span!("Executing input", contract_name = ?input.instance) tracing::info_span!("Executing input", contract_name = ?input.instance)
.in_scope(|| { .in_scope(|| {
let (leader_receipt, _, leader_diff) = match leader_state let (leader_receipt, _, leader_diff) = match leader_state
.handle_input(self.metadata, case_idx, input, self.leader_node) .handle_input(self.metadata, case_idx, &input, self.leader_node)
{ {
Ok(result) => result, Ok(result) => result,
Err(error) => { Err(error) => {
@@ -541,7 +757,7 @@ where
.handle_input( .handle_input(
self.metadata, self.metadata,
case_idx, case_idx,
input, &input,
self.follower_node, self.follower_node,
) { ) {
Ok(result) => result, Ok(result) => result,
@@ -589,14 +805,6 @@ where
tracing::trace!("Leader logs: {:?}", leader_receipt.logs()); tracing::trace!("Leader logs: {:?}", leader_receipt.logs());
tracing::trace!("Follower logs: {:?}", follower_receipt.logs()); tracing::trace!("Follower logs: {:?}", follower_receipt.logs());
} }
if leader_receipt.status() != follower_receipt.status() {
tracing::debug!(
"Mismatch in status: leader = {}, follower = {}",
leader_receipt.status(),
follower_receipt.status()
);
}
} }
// Note: Only consider the case as having been successful after we have processed // Note: Only consider the case as having been successful after we have processed
+2 -2
View File
@@ -5,7 +5,7 @@
use revive_dt_compiler::{SolidityCompiler, revive_resolc, solc}; use revive_dt_compiler::{SolidityCompiler, revive_resolc, solc};
use revive_dt_config::TestingPlatform; use revive_dt_config::TestingPlatform;
use revive_dt_node::{geth, kitchensink::KitchensinkNode}; use revive_dt_node::{Node, geth, kitchensink::KitchensinkNode};
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
pub mod common; pub mod common;
@@ -15,7 +15,7 @@ pub mod driver;
/// ///
/// For this we need a blockchain node implementation and a compiler. /// For this we need a blockchain node implementation and a compiler.
pub trait Platform { pub trait Platform {
type Blockchain: EthereumNode; type Blockchain: EthereumNode + Node;
type Compiler: SolidityCompiler; type Compiler: SolidityCompiler;
/// Returns the matching [TestingPlatform] of the [revive_dt_config::Arguments]. /// Returns the matching [TestingPlatform] of the [revive_dt_config::Arguments].
+32 -1
View File
@@ -1,6 +1,10 @@
use serde::Deserialize; use serde::Deserialize;
use crate::{define_wrapper_type, input::Input, mode::Mode}; use crate::{
define_wrapper_type,
input::{Expected, Input},
mode::Mode,
};
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)] #[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
pub struct Case { pub struct Case {
@@ -9,6 +13,33 @@ pub struct Case {
pub modes: Option<Vec<Mode>>, pub modes: Option<Vec<Mode>>,
pub inputs: Vec<Input>, pub inputs: Vec<Input>,
pub group: Option<String>, pub group: Option<String>,
pub expected: Option<Expected>,
}
impl Case {
pub fn inputs_iterator(&self) -> impl Iterator<Item = Input> {
let inputs_len = self.inputs.len();
self.inputs
.clone()
.into_iter()
.enumerate()
.map(move |(idx, mut input)| {
if idx + 1 == inputs_len {
if input.expected.is_none() {
input.expected = self.expected.clone();
}
// TODO: What does it mean for us to have an `expected` field on the case itself
// but the final input also has an expected field that doesn't match the one on
// the case? What are we supposed to do with that final expected field on the
// case?
input
} else {
input
}
})
}
} }
define_wrapper_type!( define_wrapper_type!(
+134 -48
View File
@@ -7,13 +7,13 @@ use alloy::{
primitives::{Address, Bytes, U256}, primitives::{Address, Bytes, U256},
rpc::types::TransactionRequest, rpc::types::TransactionRequest,
}; };
use alloy_primitives::{FixedBytes, utils::parse_units};
use semver::VersionReq; use semver::VersionReq;
use serde::Deserialize; use serde::{Deserialize, Serialize};
use serde_json::Value;
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
use crate::metadata::ContractInstance; use crate::{define_wrapper_type, metadata::ContractInstance};
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
pub struct Input { pub struct Input {
@@ -26,7 +26,7 @@ pub struct Input {
#[serde(default)] #[serde(default)]
pub calldata: Calldata, pub calldata: Calldata,
pub expected: Option<Expected>, pub expected: Option<Expected>,
pub value: Option<String>, pub value: Option<EtherValue>,
pub storage: Option<HashMap<String, Calldata>>, pub storage: Option<HashMap<String, Calldata>>,
} }
@@ -40,16 +40,24 @@ pub enum Expected {
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
pub struct ExpectedOutput { pub struct ExpectedOutput {
compiler_version: Option<VersionReq>, pub compiler_version: Option<VersionReq>,
return_data: Option<Calldata>, pub return_data: Option<Calldata>,
events: Option<Value>, pub events: Option<Vec<Event>>,
exception: Option<bool>, #[serde(default)]
pub exception: bool,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
pub struct Event {
pub address: Option<Address>,
pub topics: Vec<String>,
pub values: Calldata,
} }
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
#[serde(untagged)] #[serde(untagged)]
pub enum Calldata { pub enum Calldata {
Single(String), Single(Bytes),
Compound(Vec<String>), Compound(Vec<String>),
} }
@@ -74,6 +82,58 @@ pub enum Method {
FunctionName(String), FunctionName(String),
} }
define_wrapper_type!(
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
EtherValue(U256);
);
impl Serialize for EtherValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
format!("{} wei", self.0).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for EtherValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
let mut splitted = string.split(' ');
let (Some(value), Some(unit)) = (splitted.next(), splitted.next()) else {
return Err(serde::de::Error::custom("Failed to parse the value"));
};
let parsed = parse_units(value, unit.replace("eth", "ether"))
.map_err(|_| serde::de::Error::custom("Failed to parse units"))?
.into();
Ok(Self(parsed))
}
}
impl ExpectedOutput {
pub fn new() -> Self {
Default::default()
}
pub fn with_success(mut self) -> Self {
self.exception = false;
self
}
pub fn with_failure(mut self) -> Self {
self.exception = true;
self
}
pub fn with_calldata(mut self, calldata: Calldata) -> Self {
self.return_data = Some(calldata);
self
}
}
impl Default for Calldata { impl Default for Calldata {
fn default() -> Self { fn default() -> Self {
Self::Compound(Default::default()) Self::Compound(Default::default())
@@ -91,15 +151,25 @@ impl Calldata {
} }
} }
pub fn construct_call_data( pub fn calldata(
&self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<Vec<u8>> {
let mut buffer = Vec::<u8>::with_capacity(self.size_requirement());
self.calldata_into_slice(&mut buffer, deployed_contracts, chain_state_provider)?;
Ok(buffer)
}
pub fn calldata_into_slice(
&self, &self,
buffer: &mut Vec<u8>, buffer: &mut Vec<u8>,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
chain_state_provider: &impl EthereumNode, chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
match self { match self {
Calldata::Single(string) => { Calldata::Single(bytes) => {
alloy::hex::decode_to_slice(string, buffer)?; buffer.extend_from_slice(bytes);
} }
Calldata::Compound(items) => { Calldata::Compound(items) => {
for (arg_idx, arg) in items.iter().enumerate() { for (arg_idx, arg) in items.iter().enumerate() {
@@ -120,16 +190,46 @@ impl Calldata {
pub fn size_requirement(&self) -> usize { pub fn size_requirement(&self) -> usize {
match self { match self {
Calldata::Single(single) => (single.len() - 2) / 2, Calldata::Single(single) => single.len(),
Calldata::Compound(items) => items.len() * 32, Calldata::Compound(items) => items.len() * 32,
} }
} }
}
impl ExpectedOutput { /// Checks if this [`Calldata`] is equivalent to the passed calldata bytes.
pub fn find_all_contract_instances(&self, vec: &mut Vec<ContractInstance>) { pub fn is_equivalent(
if let Some(ref cd) = self.return_data { &self,
cd.find_all_contract_instances(vec); other: &[u8],
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<bool> {
match self {
Calldata::Single(calldata) => Ok(calldata == other),
Calldata::Compound(items) => {
// Chunking the "other" calldata into 32 byte chunks since each
// one of the items in the compound calldata represents 32 bytes
for (this, other) in items.iter().zip(other.chunks(32)) {
// The matterlabs format supports wildcards and therefore we
// also need to support them.
if this == "*" {
continue;
}
let other = if other.len() < 32 {
let mut vec = other.to_vec();
vec.resize(32, 0);
std::borrow::Cow::Owned(vec)
} else {
std::borrow::Cow::Borrowed(other)
};
let this = resolve_argument(this, deployed_contracts, chain_state_provider)?;
let other = U256::from_be_slice(&other);
if this != other {
return Ok(false);
}
}
Ok(true)
}
} }
} }
} }
@@ -153,12 +253,9 @@ impl Input {
) -> anyhow::Result<Bytes> { ) -> anyhow::Result<Bytes> {
match self.method { match self.method {
Method::Deployer | Method::Fallback => { Method::Deployer | Method::Fallback => {
let mut calldata = Vec::<u8>::with_capacity(self.calldata.size_requirement()); let calldata = self
self.calldata.construct_call_data( .calldata
&mut calldata, .calldata(deployed_contracts, chain_state_provider)?;
deployed_contracts,
chain_state_provider,
)?;
Ok(calldata.into()) Ok(calldata.into())
} }
@@ -204,7 +301,7 @@ impl Input {
// a new buffer for each one of the resolved arguments. // a new buffer for each one of the resolved arguments.
let mut calldata = Vec::<u8>::with_capacity(4 + self.calldata.size_requirement()); let mut calldata = Vec::<u8>::with_capacity(4 + self.calldata.size_requirement());
calldata.extend(function.selector().0); calldata.extend(function.selector().0);
self.calldata.construct_call_data( self.calldata.calldata_into_slice(
&mut calldata, &mut calldata,
deployed_contracts, deployed_contracts,
chain_state_provider, chain_state_provider,
@@ -222,7 +319,11 @@ impl Input {
chain_state_provider: &impl EthereumNode, chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<TransactionRequest> { ) -> anyhow::Result<TransactionRequest> {
let input_data = self.encoded_input(deployed_contracts, chain_state_provider)?; let input_data = self.encoded_input(deployed_contracts, chain_state_provider)?;
let transaction_request = TransactionRequest::default(); let transaction_request = TransactionRequest::default().from(self.caller).value(
self.value
.map(|value| value.into_inner())
.unwrap_or_default(),
);
match self.method { match self.method {
Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)), Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)),
_ => Ok(transaction_request _ => Ok(transaction_request
@@ -236,20 +337,6 @@ impl Input {
vec.push(self.instance.clone()); vec.push(self.instance.clone());
self.calldata.find_all_contract_instances(&mut vec); self.calldata.find_all_contract_instances(&mut vec);
match &self.expected {
Some(Expected::Calldata(cd)) => {
cd.find_all_contract_instances(&mut vec);
}
Some(Expected::Expected(expected)) => {
expected.find_all_contract_instances(&mut vec);
}
Some(Expected::ExpectedMany(expected)) => {
for expected in expected {
expected.find_all_contract_instances(&mut vec);
}
}
None => {}
}
vec vec
} }
@@ -259,8 +346,10 @@ fn default_instance() -> ContractInstance {
ContractInstance::new_from("Test") ContractInstance::new_from("Test")
} }
fn default_caller() -> Address { pub const fn default_caller() -> Address {
"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1".parse().unwrap() Address(FixedBytes(alloy::hex!(
"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"
)))
} }
/// This function takes in the string calldata argument provided in the JSON input and resolves it /// This function takes in the string calldata argument provided in the JSON input and resolves it
@@ -355,22 +444,19 @@ mod tests {
fn trace_transaction( fn trace_transaction(
&self, &self,
_: alloy::rpc::types::TransactionReceipt, _: &alloy::rpc::types::TransactionReceipt,
_: alloy::rpc::types::trace::geth::GethDebugTracingOptions,
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> { ) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
unimplemented!() unimplemented!()
} }
fn state_diff( fn state_diff(
&self, &self,
_: alloy::rpc::types::TransactionReceipt, _: &alloy::rpc::types::TransactionReceipt,
) -> anyhow::Result<alloy::rpc::types::trace::geth::DiffMode> { ) -> anyhow::Result<alloy::rpc::types::trace::geth::DiffMode> {
unimplemented!() unimplemented!()
} }
fn fetch_add_nonce(&self, _: Address) -> anyhow::Result<u64> {
unimplemented!()
}
fn chain_id(&self) -> anyhow::Result<alloy_primitives::ChainId> { fn chain_id(&self) -> anyhow::Result<alloy_primitives::ChainId> {
Ok(0x123) Ok(0x123)
} }
+1
View File
@@ -44,6 +44,7 @@ impl Deref for MetadataFile {
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)] #[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
pub struct Metadata { pub struct Metadata {
pub targets: Option<Vec<String>>,
pub cases: Vec<Case>, pub cases: Vec<Case>,
pub contracts: Option<BTreeMap<ContractInstance, ContractPathAndIdentifier>>, pub contracts: Option<BTreeMap<ContractInstance, ContractPathAndIdentifier>>,
// TODO: Convert into wrapper types for clarity. // TODO: Convert into wrapper types for clarity.
@@ -9,6 +9,7 @@ use tokio::{
runtime::Builder, runtime::Builder,
sync::{mpsc::UnboundedSender, oneshot}, sync::{mpsc::UnboundedSender, oneshot},
}; };
use tracing::Instrument;
/// A blocking async executor. /// A blocking async executor.
/// ///
@@ -63,6 +64,11 @@ impl BlockingExecutor {
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<TaskMessage>(); let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<TaskMessage>();
thread::spawn(move || { thread::spawn(move || {
tracing::info!(
thread_id = ?std::thread::current().id(),
"Starting async runtime thread"
);
let runtime = Builder::new_current_thread() let runtime = Builder::new_current_thread()
.enable_all() .enable_all()
.build() .build()
@@ -107,7 +113,9 @@ impl BlockingExecutor {
// in the task message. In doing this conversion, we lose some of the type information since // in the task message. In doing this conversion, we lose some of the type information since
// we're converting R => dyn Any. However, we will perform down-casting on the result to // we're converting R => dyn Any. However, we will perform down-casting on the result to
// convert it back into R. // convert it back into R.
let future = Box::pin(async move { Box::new(future.await) as Box<dyn Any + Send> }); let future = Box::pin(
async move { Box::new(future.await) as Box<dyn Any + Send> }.in_current_span(),
);
let task = TaskMessage::new(future, response_tx); let task = TaskMessage::new(future, response_tx);
if let Err(error) = STATE.tx.send(task) { if let Err(error) = STATE.tx.send(task) {
+7 -6
View File
@@ -2,7 +2,7 @@
use alloy::eips::BlockNumberOrTag; use alloy::eips::BlockNumberOrTag;
use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, U256}; use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, U256};
use alloy::rpc::types::trace::geth::{DiffMode, GethTrace}; use alloy::rpc::types::trace::geth::{DiffMode, GethDebugTracingOptions, GethTrace};
use alloy::rpc::types::{TransactionReceipt, TransactionRequest}; use alloy::rpc::types::{TransactionReceipt, TransactionRequest};
use anyhow::Result; use anyhow::Result;
@@ -15,13 +15,14 @@ pub trait EthereumNode {
fn execute_transaction(&self, transaction: TransactionRequest) -> Result<TransactionReceipt>; fn execute_transaction(&self, transaction: TransactionRequest) -> Result<TransactionReceipt>;
/// Trace the transaction in the [TransactionReceipt] and return a [GethTrace]. /// Trace the transaction in the [TransactionReceipt] and return a [GethTrace].
fn trace_transaction(&self, transaction: TransactionReceipt) -> Result<GethTrace>; fn trace_transaction(
&self,
receipt: &TransactionReceipt,
trace_options: GethDebugTracingOptions,
) -> Result<GethTrace>;
/// Returns the state diff of the transaction hash in the [TransactionReceipt]. /// Returns the state diff of the transaction hash in the [TransactionReceipt].
fn state_diff(&self, transaction: TransactionReceipt) -> Result<DiffMode>; fn state_diff(&self, receipt: &TransactionReceipt) -> Result<DiffMode>;
/// Returns the next available nonce for the given [Address].
fn fetch_add_nonce(&self, address: Address) -> Result<u64>;
/// Returns the ID of the chain that the node is on. /// Returns the ID of the chain that the node is on.
fn chain_id(&self) -> Result<ChainId>; fn chain_id(&self) -> Result<ChainId>;
+78
View File
@@ -0,0 +1,78 @@
use alloy::{
network::{Network, TransactionBuilder},
providers::{
Provider, SendableTx,
fillers::{GasFiller, TxFiller},
},
transports::TransportResult,
};
#[derive(Clone, Debug)]
pub struct FallbackGasFiller {
inner: GasFiller,
default_gas_limit: u64,
default_max_fee_per_gas: u128,
default_priority_fee: u128,
}
impl FallbackGasFiller {
pub fn new(
default_gas_limit: u64,
default_max_fee_per_gas: u128,
default_priority_fee: u128,
) -> Self {
Self {
inner: GasFiller,
default_gas_limit,
default_max_fee_per_gas,
default_priority_fee,
}
}
}
impl<N> TxFiller<N> for FallbackGasFiller
where
N: Network,
{
type Fillable = Option<<GasFiller as TxFiller<N>>::Fillable>;
fn status(
&self,
tx: &<N as Network>::TransactionRequest,
) -> alloy::providers::fillers::FillerControlFlow {
<GasFiller as TxFiller<N>>::status(&self.inner, tx)
}
fn fill_sync(&self, _: &mut alloy::providers::SendableTx<N>) {}
async fn prepare<P: Provider<N>>(
&self,
provider: &P,
tx: &<N as Network>::TransactionRequest,
) -> TransportResult<Self::Fillable> {
// Try to fetch GasFillers “fillable” (gas_price, base_fee, estimate_gas, …)
// If it errors (i.e. tx would revert under eth_estimateGas), swallow it.
match self.inner.prepare(provider, tx).await {
Ok(fill) => Ok(Some(fill)),
Err(_) => Ok(None),
}
}
async fn fill(
&self,
fillable: Self::Fillable,
mut tx: alloy::providers::SendableTx<N>,
) -> TransportResult<SendableTx<N>> {
if let Some(fill) = fillable {
// our inner GasFiller succeeded — use it
self.inner.fill(fill, tx).await
} else {
if let Some(builder) = tx.as_mut_builder() {
builder.set_gas_limit(self.default_gas_limit);
builder.set_max_fee_per_gas(self.default_max_fee_per_gas);
builder.set_max_priority_fee_per_gas(self.default_priority_fee);
}
Ok(tx)
}
}
}
+5
View File
@@ -0,0 +1,5 @@
/// This constant defines how much Wei accounts are pre-seeded with in genesis.
///
/// Note: After changing this number, check that the tests for kitchensink work as we encountered
/// some issues with different values of the initial balance on Kitchensink.
pub const INITIAL_BALANCE: u128 = 10u128.pow(37);
+63 -45
View File
@@ -1,37 +1,35 @@
//! The go-ethereum node implementation. //! The go-ethereum node implementation.
use std::{ use std::{
collections::HashMap,
fs::{File, OpenOptions, create_dir_all, remove_dir_all}, fs::{File, OpenOptions, create_dir_all, remove_dir_all},
io::{BufRead, BufReader, Read, Write}, io::{BufRead, BufReader, Read, Write},
path::PathBuf, path::PathBuf,
process::{Child, Command, Stdio}, process::{Child, Command, Stdio},
sync::{ sync::atomic::{AtomicU32, Ordering},
Mutex,
atomic::{AtomicU32, Ordering},
},
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use alloy::{ use alloy::{
eips::BlockNumberOrTag, eips::BlockNumberOrTag,
network::{Ethereum, EthereumWallet}, genesis::{Genesis, GenesisAccount},
primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, U256}, network::{Ethereum, EthereumWallet, NetworkWallet},
primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, FixedBytes, U256},
providers::{ providers::{
Provider, ProviderBuilder, Provider, ProviderBuilder,
ext::DebugApi, ext::DebugApi,
fillers::{FillProvider, TxFiller}, fillers::{CachedNonceManager, ChainIdFiller, FillProvider, NonceFiller, TxFiller},
}, },
rpc::types::{ rpc::types::{
TransactionReceipt, TransactionRequest, TransactionReceipt, TransactionRequest,
trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame}, trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame},
}, },
signers::local::PrivateKeySigner,
}; };
use revive_dt_config::Arguments; use revive_dt_config::Arguments;
use revive_dt_node_interaction::{BlockingExecutor, EthereumNode}; use revive_dt_node_interaction::{BlockingExecutor, EthereumNode};
use tracing::Level; use tracing::Level;
use crate::Node; use crate::{Node, common::FallbackGasFiller, constants::INITIAL_BALANCE};
static NODE_COUNT: AtomicU32 = AtomicU32::new(0); static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
@@ -54,7 +52,7 @@ pub struct Instance {
network_id: u64, network_id: u64,
start_timeout: u64, start_timeout: u64,
wallet: EthereumWallet, wallet: EthereumWallet,
nonces: Mutex<HashMap<Address, u64>>, nonce_manager: CachedNonceManager,
/// This vector stores [`File`] objects that we use for logging which we want to flush when the /// This vector stores [`File`] objects that we use for logging which we want to flush when the
/// node object is dropped. We do not store them in a structured fashion at the moment (in /// node object is dropped. We do not store them in a structured fashion at the moment (in
/// separate fields) as the logic that we need to apply to them is all the same regardless of /// separate fields) as the logic that we need to apply to them is all the same regardless of
@@ -82,8 +80,19 @@ impl Instance {
create_dir_all(&self.base_directory)?; create_dir_all(&self.base_directory)?;
create_dir_all(&self.logs_directory)?; create_dir_all(&self.logs_directory)?;
let mut genesis = serde_json::from_str::<Genesis>(&genesis)?;
for signer_address in
<EthereumWallet as NetworkWallet<Ethereum>>::signer_addresses(&self.wallet)
{
// Note, the use of the entry API here means that we only modify the entries for any
// account that is not in the `alloc` field of the genesis state.
genesis
.alloc
.entry(signer_address)
.or_insert(GenesisAccount::default().with_balance(U256::from(INITIAL_BALANCE)));
}
let genesis_path = self.base_directory.join(Self::GENESIS_JSON_FILE); let genesis_path = self.base_directory.join(Self::GENESIS_JSON_FILE);
File::create(&genesis_path)?.write_all(genesis.as_bytes())?; serde_json::to_writer(File::create(&genesis_path)?, &genesis)?;
let mut child = Command::new(&self.geth) let mut child = Command::new(&self.geth)
.arg("init") .arg("init")
@@ -206,8 +215,19 @@ impl Instance {
> + 'static { > + 'static {
let connection_string = self.connection_string(); let connection_string = self.connection_string();
let wallet = self.wallet.clone(); let wallet = self.wallet.clone();
// Note: We would like all providers to make use of the same nonce manager so that we have
// monotonically increasing nonces that are cached. The cached nonce manager uses Arc's in
// its implementation and therefore it means that when we clone it then it still references
// the same state.
let nonce_manager = self.nonce_manager.clone();
Box::pin(async move { Box::pin(async move {
ProviderBuilder::new() ProviderBuilder::new()
.disable_recommended_fillers()
.filler(FallbackGasFiller::new(500_000_000, 500_000_000, 1))
.filler(ChainIdFiller::default())
.filler(NonceFiller::new(nonce_manager))
.wallet(wallet) .wallet(wallet)
.connect(&connection_string) .connect(&connection_string)
.await .await
@@ -224,7 +244,7 @@ impl EthereumNode for Instance {
) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> { ) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> {
let provider = self.provider(); let provider = self.provider();
BlockingExecutor::execute(async move { BlockingExecutor::execute(async move {
let outer_span = tracing::debug_span!("Submitting transaction", ?transaction,); let outer_span = tracing::debug_span!("Submitting transaction", ?transaction);
let _outer_guard = outer_span.enter(); let _outer_guard = outer_span.enter();
let provider = provider.await?; let provider = provider.await?;
@@ -305,30 +325,28 @@ impl EthereumNode for Instance {
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))] #[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
fn trace_transaction( fn trace_transaction(
&self, &self,
transaction: TransactionReceipt, transaction: &TransactionReceipt,
trace_options: GethDebugTracingOptions,
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> { ) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig { let tx_hash = transaction.transaction_hash;
diff_mode: Some(true),
disable_code: None,
disable_storage: None,
});
let provider = self.provider(); let provider = self.provider();
BlockingExecutor::execute(async move { BlockingExecutor::execute(async move {
Ok(provider Ok(provider
.await? .await?
.debug_trace_transaction(transaction.transaction_hash, trace_options) .debug_trace_transaction(tx_hash, trace_options)
.await?) .await?)
})? })?
} }
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))] #[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
fn state_diff( fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result<DiffMode> {
&self, let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
transaction: alloy::rpc::types::TransactionReceipt, diff_mode: Some(true),
) -> anyhow::Result<DiffMode> { disable_code: None,
disable_storage: None,
});
match self match self
.trace_transaction(transaction)? .trace_transaction(transaction, trace_options)?
.try_into_pre_state_frame()? .try_into_pre_state_frame()?
{ {
PreStateFrame::Diff(diff) => Ok(diff), PreStateFrame::Diff(diff) => Ok(diff),
@@ -336,24 +354,6 @@ impl EthereumNode for Instance {
} }
} }
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
fn fetch_add_nonce(&self, address: Address) -> anyhow::Result<u64> {
let provider = self.provider();
let onchain_nonce = BlockingExecutor::execute::<anyhow::Result<_>>(async move {
provider
.await?
.get_transaction_count(address)
.await
.map_err(Into::into)
})??;
let mut nonces = self.nonces.lock().unwrap();
let current = nonces.entry(address).or_insert(onchain_nonce);
let value = *current;
*current += 1;
Ok(value)
}
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))] #[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> { fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
let provider = self.provider(); let provider = self.provider();
@@ -442,6 +442,15 @@ impl Node for Instance {
let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst); let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst);
let base_directory = geth_directory.join(id.to_string()); let base_directory = geth_directory.join(id.to_string());
let mut wallet = config.wallet();
for signer in (1..=config.private_keys_to_add)
.map(|id| U256::from(id))
.map(|id| id.to_be_bytes::<32>())
.map(|id| PrivateKeySigner::from_bytes(&FixedBytes(id)).unwrap())
{
wallet.register_signer(signer);
}
Self { Self {
connection_string: base_directory.join(Self::IPC_FILE).display().to_string(), connection_string: base_directory.join(Self::IPC_FILE).display().to_string(),
data_directory: base_directory.join(Self::DATA_DIRECTORY), data_directory: base_directory.join(Self::DATA_DIRECTORY),
@@ -452,11 +461,11 @@ impl Node for Instance {
handle: None, handle: None,
network_id: config.network_id, network_id: config.network_id,
start_timeout: config.geth_start_timeout, start_timeout: config.geth_start_timeout,
wallet: config.wallet(), wallet,
nonces: Mutex::new(HashMap::new()),
// We know that we only need to be storing 2 files so we can specify that when creating // We know that we only need to be storing 2 files so we can specify that when creating
// the vector. It's the stdout and stderr of the geth node. // the vector. It's the stdout and stderr of the geth node.
logs_file_to_flush: Vec::with_capacity(2), logs_file_to_flush: Vec::with_capacity(2),
nonce_manager: Default::default(),
} }
} }
@@ -505,6 +514,14 @@ impl Node for Instance {
.stdout; .stdout;
Ok(String::from_utf8_lossy(&output).into()) Ok(String::from_utf8_lossy(&output).into())
} }
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
fn matches_target(&self, targets: Option<&[String]>) -> bool {
match targets {
None => true,
Some(targets) => targets.iter().any(|str| str.as_str() == "evm"),
}
}
} }
impl Drop for Instance { impl Drop for Instance {
@@ -517,6 +534,7 @@ impl Drop for Instance {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use revive_dt_config::Arguments; use revive_dt_config::Arguments;
use temp_dir::TempDir; use temp_dir::TempDir;
use crate::{GENESIS_JSON, Node}; use crate::{GENESIS_JSON, Node};
+99 -81
View File
@@ -1,35 +1,34 @@
use std::{ use std::{
collections::HashMap,
fs::{File, OpenOptions, create_dir_all, remove_dir_all}, fs::{File, OpenOptions, create_dir_all, remove_dir_all},
io::{BufRead, Write}, io::{BufRead, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
process::{Child, Command, Stdio}, process::{Child, Command, Stdio},
sync::{ sync::atomic::{AtomicU32, Ordering},
Mutex,
atomic::{AtomicU32, Ordering},
},
time::Duration, time::Duration,
}; };
use alloy::{ use alloy::{
consensus::{BlockHeader, TxEnvelope}, consensus::{BlockHeader, TxEnvelope},
eips::BlockNumberOrTag, eips::BlockNumberOrTag,
hex, genesis::{Genesis, GenesisAccount},
network::{ network::{
Ethereum, EthereumWallet, Network, TransactionBuilder, TransactionBuilderError, Ethereum, EthereumWallet, Network, NetworkWallet, TransactionBuilder,
UnbuiltTransactionError, TransactionBuilderError, UnbuiltTransactionError,
},
primitives::{
Address, B64, B256, BlockHash, BlockNumber, BlockTimestamp, Bloom, Bytes, FixedBytes, U256,
}, },
primitives::{Address, B64, B256, BlockHash, BlockNumber, BlockTimestamp, Bloom, Bytes, U256},
providers::{ providers::{
Provider, ProviderBuilder, Provider, ProviderBuilder,
ext::DebugApi, ext::DebugApi,
fillers::{FillProvider, TxFiller}, fillers::{CachedNonceManager, ChainIdFiller, FillProvider, NonceFiller, TxFiller},
}, },
rpc::types::{ rpc::types::{
TransactionReceipt, TransactionReceipt,
eth::{Block, Header, Transaction}, eth::{Block, Header, Transaction},
trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame}, trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame},
}, },
signers::local::PrivateKeySigner,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{Value as JsonValue, json}; use serde_json::{Value as JsonValue, json};
@@ -40,7 +39,7 @@ use tracing::Level;
use revive_dt_config::Arguments; use revive_dt_config::Arguments;
use revive_dt_node_interaction::{BlockingExecutor, EthereumNode}; use revive_dt_node_interaction::{BlockingExecutor, EthereumNode};
use crate::Node; use crate::{Node, common::FallbackGasFiller, constants::INITIAL_BALANCE};
static NODE_COUNT: AtomicU32 = AtomicU32::new(0); static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
@@ -55,7 +54,7 @@ pub struct KitchensinkNode {
logs_directory: PathBuf, logs_directory: PathBuf,
process_substrate: Option<Child>, process_substrate: Option<Child>,
process_proxy: Option<Child>, process_proxy: Option<Child>,
nonces: Mutex<HashMap<Address, u64>>, nonce_manager: CachedNonceManager,
/// This vector stores [`File`] objects that we use for logging which we want to flush when the /// This vector stores [`File`] objects that we use for logging which we want to flush when the
/// node object is dropped. We do not store them in a structured fashion at the moment (in /// node object is dropped. We do not store them in a structured fashion at the moment (in
/// separate fields) as the logic that we need to apply to them is all the same regardless of /// separate fields) as the logic that we need to apply to them is all the same regardless of
@@ -127,7 +126,20 @@ impl KitchensinkNode {
None None
}) })
.collect(); .collect();
let mut eth_balances = self.extract_balance_from_genesis_file(genesis)?; let mut eth_balances = {
let mut genesis = serde_json::from_str::<Genesis>(genesis)?;
for signer_address in
<EthereumWallet as NetworkWallet<Ethereum>>::signer_addresses(&self.wallet)
{
// Note, the use of the entry API here means that we only modify the entries for any
// account that is not in the `alloc` field of the genesis state.
genesis
.alloc
.entry(signer_address)
.or_insert(GenesisAccount::default().with_balance(U256::from(INITIAL_BALANCE)));
}
self.extract_balance_from_genesis_file(&genesis)?
};
merged_balances.append(&mut eth_balances); merged_balances.append(&mut eth_balances);
chainspec_json["genesis"]["runtimeGenesis"]["patch"]["balances"]["balances"] = chainspec_json["genesis"]["runtimeGenesis"]["patch"]["balances"]["balances"] =
@@ -241,42 +253,27 @@ impl KitchensinkNode {
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
fn extract_balance_from_genesis_file( fn extract_balance_from_genesis_file(
&self, &self,
genesis_str: &str, genesis: &Genesis,
) -> anyhow::Result<Vec<(String, u128)>> { ) -> anyhow::Result<Vec<(String, u128)>> {
let genesis_json: JsonValue = serde_json::from_str(genesis_str)?; genesis
let alloc = genesis_json .alloc
.get("alloc") .iter()
.and_then(|a| a.as_object()) .try_fold(Vec::new(), |mut vec, (address, acc)| {
.ok_or_else(|| anyhow::anyhow!("Missing 'alloc' in genesis"))?; let substrate_address = Self::eth_to_substrate_address(address);
let balance = acc.balance.try_into()?;
let mut balances = Vec::new(); vec.push((substrate_address, balance));
for (eth_addr, obj) in alloc.iter() { Ok(vec)
let balance_str = obj.get("balance").and_then(|b| b.as_str()).unwrap_or("0"); })
let balance = if balance_str.starts_with("0x") {
u128::from_str_radix(balance_str.trim_start_matches("0x"), 16)?
} else {
balance_str.parse::<u128>()?
};
let substrate_addr = Self::eth_to_substrate_address(eth_addr)?;
balances.push((substrate_addr.clone(), balance));
}
Ok(balances)
} }
fn eth_to_substrate_address(eth_addr: &str) -> anyhow::Result<String> { fn eth_to_substrate_address(address: &Address) -> String {
let eth_bytes = hex::decode(eth_addr.trim_start_matches("0x"))?; let eth_bytes = address.0.0;
if eth_bytes.len() != 20 {
anyhow::bail!(
"Invalid Ethereum address length: expected 20 bytes, got {}",
eth_bytes.len()
);
}
let mut padded = [0xEEu8; 32]; let mut padded = [0xEEu8; 32];
padded[..20].copy_from_slice(&eth_bytes); padded[..20].copy_from_slice(&eth_bytes);
let account_id = AccountId32::from(padded); let account_id = AccountId32::from(padded);
Ok(account_id.to_ss58check()) account_id.to_ss58check()
} }
fn wait_ready(logs_file_path: &Path, marker: &str, timeout: Duration) -> anyhow::Result<()> { fn wait_ready(logs_file_path: &Path, marker: &str, timeout: Duration) -> anyhow::Result<()> {
@@ -350,9 +347,24 @@ impl KitchensinkNode {
> + 'static { > + 'static {
let connection_string = self.connection_string(); let connection_string = self.connection_string();
let wallet = self.wallet.clone(); let wallet = self.wallet.clone();
// Note: We would like all providers to make use of the same nonce manager so that we have
// monotonically increasing nonces that are cached. The cached nonce manager uses Arc's in
// its implementation and therefore it means that when we clone it then it still references
// the same state.
let nonce_manager = self.nonce_manager.clone();
Box::pin(async move { Box::pin(async move {
ProviderBuilder::new() ProviderBuilder::new()
.disable_recommended_fillers()
.network::<KitchenSinkNetwork>() .network::<KitchenSinkNetwork>()
.filler(FallbackGasFiller::new(
30_000_000,
200_000_000_000,
3_000_000_000,
))
.filler(ChainIdFiller::default())
.filler(NonceFiller::new(nonce_manager))
.wallet(wallet) .wallet(wallet)
.connect(&connection_string) .connect(&connection_string)
.await .await
@@ -384,27 +396,28 @@ impl EthereumNode for KitchensinkNode {
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
fn trace_transaction( fn trace_transaction(
&self, &self,
transaction: TransactionReceipt, transaction: &TransactionReceipt,
trace_options: GethDebugTracingOptions,
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> { ) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig { let tx_hash = transaction.transaction_hash;
diff_mode: Some(true),
disable_code: None,
disable_storage: None,
});
let provider = self.provider(); let provider = self.provider();
BlockingExecutor::execute(async move { BlockingExecutor::execute(async move {
Ok(provider Ok(provider
.await? .await?
.debug_trace_transaction(transaction.transaction_hash, trace_options) .debug_trace_transaction(tx_hash, trace_options)
.await?) .await?)
})? })?
} }
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
fn state_diff(&self, transaction: TransactionReceipt) -> anyhow::Result<DiffMode> { fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result<DiffMode> {
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
diff_mode: Some(true),
disable_code: None,
disable_storage: None,
});
match self match self
.trace_transaction(transaction)? .trace_transaction(transaction, trace_options)?
.try_into_pre_state_frame()? .try_into_pre_state_frame()?
{ {
PreStateFrame::Diff(diff) => Ok(diff), PreStateFrame::Diff(diff) => Ok(diff),
@@ -412,24 +425,6 @@ impl EthereumNode for KitchensinkNode {
} }
} }
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
fn fetch_add_nonce(&self, address: Address) -> anyhow::Result<u64> {
let provider = self.provider();
let onchain_nonce = BlockingExecutor::execute::<anyhow::Result<_>>(async move {
provider
.await?
.get_transaction_count(address)
.await
.map_err(Into::into)
})??;
let mut nonces = self.nonces.lock().unwrap();
let current = nonces.entry(address).or_insert(onchain_nonce);
let value = *current;
*current += 1;
Ok(value)
}
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))] #[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> { fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
let provider = self.provider(); let provider = self.provider();
@@ -519,17 +514,26 @@ impl Node for KitchensinkNode {
let base_directory = kitchensink_directory.join(id.to_string()); let base_directory = kitchensink_directory.join(id.to_string());
let logs_directory = base_directory.join(Self::LOGS_DIRECTORY); let logs_directory = base_directory.join(Self::LOGS_DIRECTORY);
let mut wallet = config.wallet();
for signer in (1..=config.private_keys_to_add)
.map(|id| U256::from(id))
.map(|id| id.to_be_bytes::<32>())
.map(|id| PrivateKeySigner::from_bytes(&FixedBytes(id)).unwrap())
{
wallet.register_signer(signer);
}
Self { Self {
id, id,
substrate_binary: config.kitchensink.clone(), substrate_binary: config.kitchensink.clone(),
eth_proxy_binary: config.eth_proxy.clone(), eth_proxy_binary: config.eth_proxy.clone(),
rpc_url: String::new(), rpc_url: String::new(),
wallet: config.wallet(), wallet,
base_directory, base_directory,
logs_directory, logs_directory,
process_substrate: None, process_substrate: None,
process_proxy: None, process_proxy: None,
nonces: Mutex::new(HashMap::new()), nonce_manager: Default::default(),
// We know that we only need to be storing 4 files so we can specify that when creating // We know that we only need to be storing 4 files so we can specify that when creating
// the vector. It's the stdout and stderr of the substrate-node and the eth-rpc. // the vector. It's the stdout and stderr of the substrate-node and the eth-rpc.
logs_file_to_flush: Vec::with_capacity(4), logs_file_to_flush: Vec::with_capacity(4),
@@ -585,6 +589,14 @@ impl Node for KitchensinkNode {
.stdout; .stdout;
Ok(String::from_utf8_lossy(&output).into()) Ok(String::from_utf8_lossy(&output).into())
} }
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
fn matches_target(&self, targets: Option<&[String]>) -> bool {
match targets {
None => true,
Some(targets) => targets.iter().any(|str| str.as_str() == "pvm"),
}
}
} }
impl Drop for KitchensinkNode { impl Drop for KitchensinkNode {
@@ -640,6 +652,12 @@ impl TransactionBuilder<KitchenSinkNetwork> for <Ethereum as Network>::Transacti
) )
} }
fn take_nonce(&mut self) -> Option<u64> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::take_nonce(
self,
)
}
fn input(&self) -> Option<&alloy::primitives::Bytes> { fn input(&self) -> Option<&alloy::primitives::Bytes> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::input(self) <<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::input(self)
} }
@@ -1020,7 +1038,7 @@ mod tests {
use alloy::rpc::types::TransactionRequest; use alloy::rpc::types::TransactionRequest;
use revive_dt_config::Arguments; use revive_dt_config::Arguments;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::LazyLock; use std::sync::{LazyLock, Mutex};
use temp_dir::TempDir; use temp_dir::TempDir;
use std::fs; use std::fs;
@@ -1129,12 +1147,12 @@ mod tests {
let contents = fs::read_to_string(&final_chainspec_path).expect("Failed to read chainspec"); let contents = fs::read_to_string(&final_chainspec_path).expect("Failed to read chainspec");
// Validate that the Substrate addresses derived from the Ethereum addresses are in the file // Validate that the Substrate addresses derived from the Ethereum addresses are in the file
let first_eth_addr = let first_eth_addr = KitchensinkNode::eth_to_substrate_address(
KitchensinkNode::eth_to_substrate_address("90F8bf6A479f320ead074411a4B0e7944Ea8c9C1") &"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1".parse().unwrap(),
.unwrap(); );
let second_eth_addr = let second_eth_addr = KitchensinkNode::eth_to_substrate_address(
KitchensinkNode::eth_to_substrate_address("Ab8483F64d9C6d1EcF9b849Ae677dD3315835cb2") &"Ab8483F64d9C6d1EcF9b849Ae677dD3315835cb2".parse().unwrap(),
.unwrap(); );
assert!( assert!(
contents.contains(&first_eth_addr), contents.contains(&first_eth_addr),
@@ -1162,7 +1180,7 @@ mod tests {
let node = KitchensinkNode::new(&test_config().0); let node = KitchensinkNode::new(&test_config().0);
let result = node let result = node
.extract_balance_from_genesis_file(genesis_json) .extract_balance_from_genesis_file(&serde_json::from_str(genesis_json).unwrap())
.unwrap(); .unwrap();
let result_map: std::collections::HashMap<_, _> = result.into_iter().collect(); let result_map: std::collections::HashMap<_, _> = result.into_iter().collect();
@@ -1192,7 +1210,7 @@ mod tests {
]; ];
for eth_addr in eth_addresses { for eth_addr in eth_addresses {
let ss58 = KitchensinkNode::eth_to_substrate_address(eth_addr).unwrap(); let ss58 = KitchensinkNode::eth_to_substrate_address(&eth_addr.parse().unwrap());
println!("Ethereum: {eth_addr} -> Substrate SS58: {ss58}"); println!("Ethereum: {eth_addr} -> Substrate SS58: {ss58}");
} }
@@ -1220,7 +1238,7 @@ mod tests {
]; ];
for (eth_addr, expected_ss58) in cases { for (eth_addr, expected_ss58) in cases {
let result = KitchensinkNode::eth_to_substrate_address(eth_addr).unwrap(); let result = KitchensinkNode::eth_to_substrate_address(&eth_addr.parse().unwrap());
assert_eq!( assert_eq!(
result, expected_ss58, result, expected_ss58,
"Mismatch for Ethereum address {eth_addr}" "Mismatch for Ethereum address {eth_addr}"
+6
View File
@@ -3,6 +3,8 @@
use revive_dt_config::Arguments; use revive_dt_config::Arguments;
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
pub mod common;
pub mod constants;
pub mod geth; pub mod geth;
pub mod kitchensink; pub mod kitchensink;
pub mod pool; pub mod pool;
@@ -30,4 +32,8 @@ pub trait Node: EthereumNode {
/// Returns the node version. /// Returns the node version.
fn version(&self) -> anyhow::Result<String>; fn version(&self) -> anyhow::Result<String>;
/// Given a list of targets from the metadata file, this function determines if the metadata
/// file can be ran on this node or not.
fn matches_target(&self, targets: Option<&[String]>) -> bool;
} }
+1 -5
View File
@@ -33,9 +33,5 @@
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x00", "timestamp": "0x00",
"alloc": { "alloc": {}
"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1": {
"balance": "1000000000000000000"
}
}
} }