Compare commits

...

20 Commits

Author SHA1 Message Date
Omar Abdulla 8c09428be5 Update the comment 2025-07-21 10:57:57 +03:00
Omar Abdulla ac6387b6f0 Support different callers 2025-07-21 10:34:57 +03:00
Omar Abdulla e5a3f0aee9 Fix tests 2025-07-21 07:31:10 +03:00
Omar Abdulla 3cdf57f7c3 Cached nonce allocator 2025-07-21 07:19:44 +03:00
Omar Abdulla dab8ffe520 Add support for exceptions 2025-07-18 21:00:44 +03:00
Omar Abdulla c913a8222f Merge branch 'bugfix/function-signature' into feature/handle-exceptions 2025-07-18 17:25:17 +03:00
Omar Abdulla c8cef4834f Allow for the use of function signatures 2025-07-18 16:37:15 +03:00
Omar Abdulla ca59a1f6a9 Handle calldata better 2025-07-18 15:52:40 +03:00
Omar Abdulla adc0c44cde Merge remote-tracking branch 'origin/main' into refactor/contract-deployment-and-input-handling 2025-07-18 15:18:27 +03:00
Omar Abdulla 811e17136b Merge remote-tracking branch 'origin/main' into refactor/contract-deployment-and-input-handling 2025-07-18 15:11:40 +03:00
Omar Abdulla ba32bad6b3 Fix edge-case in deployment order 2025-07-17 22:26:49 +03:00
Omar Abdulla bb754cba4f Correct comment 2025-07-17 18:53:24 +03:00
Omar Abdulla c858bbe66d Ignore macro doc comment tests 2025-07-17 18:28:23 +03:00
Omar Abdulla 906878f06a Fix edge-case in input handling 2025-07-17 18:00:49 +03:00
Omar Abdulla 9a71369e8a Implement the new input handling logic 2025-07-17 17:46:40 +03:00
Omar Abdulla 84ab873b46 Impl new_from for wrapper types 2025-07-17 15:33:28 +03:00
Omar Abdulla 2ef6f7ba63 Make metadata structs more typed 2025-07-17 15:31:18 +03:00
Omar Abdulla 38e6140a7c Remove unneeded use of two HashMaps 2025-07-17 14:41:48 +03:00
Omar Abdulla ca6c5529e2 Move FilesWithExtensionIterator to core::common 2025-07-17 14:32:55 +03:00
Omar Abdulla 038a2db53c Add support for wrapper types 2025-07-17 14:22:18 +03:00
21 changed files with 1809 additions and 728 deletions
Generated
+79 -72
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",
@@ -336,7 +340,7 @@ dependencies = [
"derive_more 2.0.1", "derive_more 2.0.1",
"foldhash", "foldhash",
"hashbrown 0.15.3", "hashbrown 0.15.3",
"indexmap 2.9.0", "indexmap 2.10.0",
"itoa", "itoa",
"k256", "k256",
"keccak-asm", "keccak-asm",
@@ -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",
@@ -597,7 +601,7 @@ dependencies = [
"alloy-sol-macro-input", "alloy-sol-macro-input",
"const-hex", "const-hex",
"heck", "heck",
"indexmap 2.9.0", "indexmap 2.10.0",
"proc-macro-error2", "proc-macro-error2",
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -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"
@@ -2400,7 +2417,7 @@ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"http", "http",
"indexmap 2.9.0", "indexmap 2.10.0",
"slab", "slab",
"tokio", "tokio",
"tokio-util", "tokio-util",
@@ -2842,9 +2859,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.9.0" version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.15.3", "hashbrown 0.15.3",
@@ -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",
] ]
@@ -3962,6 +3980,7 @@ dependencies = [
"alloy", "alloy",
"anyhow", "anyhow",
"clap", "clap",
"indexmap 2.10.0",
"rayon", "rayon",
"revive-dt-compiler", "revive-dt-compiler",
"revive-dt-config", "revive-dt-config",
@@ -4507,7 +4526,7 @@ dependencies = [
"chrono", "chrono",
"hex", "hex",
"indexmap 1.9.3", "indexmap 1.9.3",
"indexmap 2.9.0", "indexmap 2.10.0",
"serde", "serde",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
@@ -5396,7 +5415,7 @@ version = "0.22.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
dependencies = [ dependencies = [
"indexmap 2.9.0", "indexmap 2.10.0",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
@@ -5488,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"
+3 -1
View File
@@ -51,6 +51,7 @@ tracing-subscriber = { version = "0.3.19", default-features = false, features =
"json", "json",
"env-filter", "env-filter",
] } ] }
indexmap = { version = "2.10.0", default-features = false }
# revive compiler # revive compiler
revive-solc-json-interface = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" } revive-solc-json-interface = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" }
@@ -58,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",
@@ -72,6 +73,7 @@ features = [
"network", "network",
"serde", "serde",
"rpc-types-eth", "rpc-types-eth",
"genesis",
] ]
[profile.bench] [profile.bench]
+326
View File
@@ -0,0 +1,326 @@
{
"modes": [
"Y >=0.8.9",
"E",
"I"
],
"cases": [
{
"name": "first",
"inputs": [
{
"instance": "WBTC_1",
"method": "#deployer",
"calldata": [
"0x40",
"0x80",
"4",
"0x5742544300000000000000000000000000000000000000000000000000000000",
"14",
"0x5772617070656420425443000000000000000000000000000000000000000000"
],
"expected": [
"WBTC_1.address"
]
},
{
"instance": "WBTC_2",
"method": "#deployer",
"calldata": [
"0x40",
"0x80",
"4",
"0x5742544300000000000000000000000000000000000000000000000000000000",
"14",
"0x5772617070656420425443000000000000000000000000000000000000000000"
],
"expected": [
"WBTC_2.address"
]
},
{
"instance": "Mooniswap",
"method": "#deployer",
"calldata": [
"0x0000000000000000000000000000000000000000000000000000000000000060",
"0x00000000000000000000000000000000000000000000000000000000000000c0",
"0x0000000000000000000000000000000000000000000000000000000000000100",
"0x0000000000000000000000000000000000000000000000000000000000000002",
"WBTC_1.address",
"WBTC_2.address",
"4",
"0x5742544300000000000000000000000000000000000000000000000000000000",
"14",
"0x5772617070656420425443000000000000000000000000000000000000000000"
],
"expected": {
"return_data": [
"Mooniswap.address"
],
"events": [
{
"topics": [
"0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0xdeadbeef01000000000000000000000000000000"
],
"values": []
}
],
"exception": false
}
},
{
"instance": "WBTC_1",
"method": "_mint",
"calldata": [
"0xdeadbeef00000000000000000000000000000042",
"1000000000"
],
"expected": {
"return_data": [],
"events": [
{
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0xdeadbeef00000000000000000000000000000042"
],
"values": [
"1000000000"
]
}
],
"exception": false
}
},
{
"instance": "WBTC_2",
"method": "_mint",
"calldata": [
"0xdeadbeef00000000000000000000000000000042",
"1000000000"
],
"expected": {
"return_data": [],
"events": [
{
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0xdeadbeef00000000000000000000000000000042"
],
"values": [
"1000000000"
]
}
],
"exception": false
}
},
{
"instance": "WBTC_1",
"caller": "0xdeadbeef00000000000000000000000000000042",
"method": "approve",
"calldata": [
"Mooniswap.address",
"500000000"
],
"expected": {
"return_data": [
"0x0000000000000000000000000000000000000000000000000000000000000001"
],
"events": [
{
"topics": [
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
"0xdeadbeef00000000000000000000000000000042",
"Mooniswap.address"
],
"values": [
"500000000"
]
}
],
"exception": false
}
},
{
"instance": "WBTC_2",
"caller": "0xdeadbeef00000000000000000000000000000042",
"method": "approve",
"calldata": [
"Mooniswap.address",
"500000000"
],
"expected": {
"return_data": [
"0x0000000000000000000000000000000000000000000000000000000000000001"
],
"events": [
{
"topics": [
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
"0xdeadbeef00000000000000000000000000000042",
"Mooniswap.address"
],
"values": [
"500000000"
]
}
],
"exception": false
}
},
{
"instance": "Mooniswap",
"caller": "0xdeadbeef00000000000000000000000000000042",
"method": "deposit",
"calldata": [
"0x0000000000000000000000000000000000000000000000000000000000000040",
"0x00000000000000000000000000000000000000000000000000000000000000a0",
"0x0000000000000000000000000000000000000000000000000000000000000002",
"10000000",
"10000000",
"0x0000000000000000000000000000000000000000000000000000000000000002",
"1000000",
"1000000"
],
"expected": {
"return_data": [
"10000000"
],
"events": [
{
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"Mooniswap.address"
],
"values": [
"1000"
]
},
{
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0xdeadbeef00000000000000000000000000000042",
"Mooniswap.address"
],
"values": [
"10000000"
]
},
{
"topics": [
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
"0xdeadbeef00000000000000000000000000000042",
"Mooniswap.address"
],
"values": [
"490000000"
]
},
{
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0xdeadbeef00000000000000000000000000000042",
"Mooniswap.address"
],
"values": [
"10000000"
]
},
{
"topics": [
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
"0xdeadbeef00000000000000000000000000000042",
"Mooniswap.address"
],
"values": [
"490000000"
]
},
{
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0xdeadbeef00000000000000000000000000000042"
],
"values": [
"10000000"
]
},
{
"topics": [
"0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4",
"0xdeadbeef00000000000000000000000000000042"
],
"values": [
"10000000"
]
}
],
"exception": false
}
},
{
"instance": "Mooniswap",
"caller": "0xdeadbeef00000000000000000000000000000042",
"method": "swap",
"calldata": [
"WBTC_1.address",
"WBTC_2.address",
"5000",
"5000",
"0"
]
}
],
"expected": {
"return_data": [
"5000"
],
"events": [
{
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0xdeadbeef00000000000000000000000000000042",
"Mooniswap.address"
],
"values": [
"5000"
]
},
{
"topics": [
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
"0xdeadbeef00000000000000000000000000000042",
"Mooniswap.address"
],
"values": [
"489995000"
]
}
],
"exception": false
}
}
],
"contracts": {
"Mooniswap": "Mooniswap.sol:Mooniswap",
"WBTC_1": "ERC20/ERC20.sol:ERC20",
"WBTC_2": "ERC20/ERC20.sol:ERC20",
"VirtualBalance": "Mooniswap.sol:VirtualBalance",
"Math": "math/Math.sol:Math"
},
"libraries": {
"Mooniswap.sol": {
"VirtualBalance": "VirtualBalance"
},
"math/Math.sol": {
"Math": "Math"
}
},
"group": "Real life"
}
+9 -7
View File
@@ -123,16 +123,18 @@ impl Arguments {
panic!("should have a workdir configured") panic!("should have a workdir configured")
} }
/// Try to parse `self.account` into a [PrivateKeySigner], pub fn signer(&self) -> PrivateKeySigner {
/// panicing on error. self.account
pub fn wallet(&self) -> EthereumWallet {
let signer = self
.account
.parse::<PrivateKeySigner>() .parse::<PrivateKeySigner>()
.unwrap_or_else(|error| { .unwrap_or_else(|error| {
panic!("private key '{}' parsing error: {error}", self.account); panic!("private key '{}' parsing error: {error}", self.account);
}); })
EthereumWallet::new(signer) }
/// Try to parse `self.account` into a [PrivateKeySigner],
/// panicing on error.
pub fn wallet(&self) -> EthereumWallet {
EthereumWallet::new(self.signer())
} }
} }
+1
View File
@@ -23,6 +23,7 @@ revive-dt-report = { workspace = true }
alloy = { workspace = true } alloy = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
clap = { workspace = true } clap = { workspace = true }
indexmap = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
tracing-subscriber = { workspace = true } tracing-subscriber = { workspace = true }
rayon = { workspace = true } rayon = { workspace = true }
+73
View File
@@ -0,0 +1,73 @@
use std::{borrow::Cow, collections::HashSet, path::PathBuf};
/// An iterator that finds files of a certain extension in the provided directory. You can think of
/// this a glob pattern similar to: `${path}/**/*.md`
pub struct FilesWithExtensionIterator {
/// The set of allowed extensions that that match the requirement and that should be returned
/// when found.
allowed_extensions: HashSet<Cow<'static, str>>,
/// The set of directories to visit next. This iterator does BFS and so these directories will
/// only be visited if we can't find any files in our state.
directories_to_search: Vec<PathBuf>,
/// The set of files matching the allowed extensions that were found. If there are entries in
/// this vector then they will be returned when the [`Iterator::next`] method is called. If not
/// then we visit one of the next directories to visit.
files_matching_allowed_extensions: Vec<PathBuf>,
}
impl FilesWithExtensionIterator {
pub fn new(root_directory: PathBuf) -> Self {
Self {
allowed_extensions: Default::default(),
directories_to_search: vec![root_directory],
files_matching_allowed_extensions: Default::default(),
}
}
pub fn with_allowed_extension(
mut self,
allowed_extension: impl Into<Cow<'static, str>>,
) -> Self {
self.allowed_extensions.insert(allowed_extension.into());
self
}
}
impl Iterator for FilesWithExtensionIterator {
type Item = PathBuf;
fn next(&mut self) -> Option<Self::Item> {
if let Some(file_path) = self.files_matching_allowed_extensions.pop() {
return Some(file_path);
};
let directory_to_search = self.directories_to_search.pop()?;
// Read all of the entries in the directory. If we failed to read this dir's entires then we
// elect to just ignore it and look in the next directory, we do that by calling the next
// method again on the iterator, which is an intentional decision that we made here instead
// of panicking.
let Ok(dir_entries) = std::fs::read_dir(directory_to_search) else {
return self.next();
};
for entry in dir_entries.flatten() {
let entry_path = entry.path();
if entry_path.is_dir() {
self.directories_to_search.push(entry_path)
} else if entry_path.is_file()
&& entry_path.extension().is_some_and(|ext| {
self.allowed_extensions
.iter()
.any(|allowed| ext.eq_ignore_ascii_case(allowed.as_ref()))
})
{
self.files_matching_allowed_extensions.push(entry_path)
}
}
self.next()
}
}
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -1,13 +1,14 @@
//! The revive differential testing core library. //! The revive differential testing core library.
//! //!
//! This crate defines the testing configuration and //! This crate defines the testing configuration and
//! provides a helper utilty to execute tests. //! provides a helper utility to execute tests.
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::{geth, kitchensink::KitchensinkNode};
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
pub mod common;
pub mod driver; pub mod driver;
/// One platform can be tested differentially against another. /// One platform can be tested differentially against another.
+31 -8
View File
@@ -1,5 +1,6 @@
use std::{collections::HashMap, sync::LazyLock}; use std::{collections::HashMap, sync::LazyLock};
use alloy::primitives::Address;
use clap::Parser; use clap::Parser;
use rayon::{ThreadPoolBuilder, prelude::*}; use rayon::{ThreadPoolBuilder, prelude::*};
@@ -8,7 +9,7 @@ use revive_dt_core::{
Geth, Kitchensink, Platform, Geth, Kitchensink, Platform,
driver::{Driver, State}, driver::{Driver, State},
}; };
use revive_dt_format::{corpus::Corpus, metadata::MetadataFile}; use revive_dt_format::{corpus::Corpus, input::default_caller, metadata::MetadataFile};
use revive_dt_node::pool::NodePool; use revive_dt_node::pool::NodePool;
use revive_dt_report::reporter::{Report, Span}; use revive_dt_report::reporter::{Report, Span};
use temp_dir::TempDir; use temp_dir::TempDir;
@@ -20,12 +21,24 @@ static TEMP_DIR: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
let args = init_cli()?; let args = init_cli()?;
let corpora = collect_corpora(&args)?;
let additional_callers = corpora
.values()
.flat_map(|value| value.iter().map(|metadata| &metadata.cases))
.flat_map(|case| case.iter().map(|case| &case.inputs))
.flatten()
.map(|input| input.caller)
.filter(|caller| caller != &default_caller())
.collect::<Vec<_>>();
tracing::debug!(?additional_callers, "Discovered callers");
for (corpus, tests) in collect_corpora(&args)? { for (corpus, tests) in collect_corpora(&args)? {
let span = Span::new(corpus, args.clone())?; let span = Span::new(corpus, args.clone())?;
match &args.compile_only { match &args.compile_only {
Some(platform) => compile_corpus(&args, &tests, platform, span), Some(platform) => compile_corpus(&args, &tests, platform, span),
None => execute_corpus(&args, &tests, span)?, None => execute_corpus(&args, &tests, &additional_callers, span)?,
} }
Report::save()?; Report::save()?;
@@ -83,15 +96,20 @@ fn collect_corpora(args: &Arguments) -> anyhow::Result<HashMap<Corpus, Vec<Metad
Ok(corpora) Ok(corpora)
} }
fn run_driver<L, F>(args: &Arguments, tests: &[MetadataFile], span: Span) -> anyhow::Result<()> fn run_driver<L, F>(
args: &Arguments,
tests: &[MetadataFile],
additional_callers: &[Address],
span: Span,
) -> anyhow::Result<()>
where where
L: Platform, L: Platform,
F: Platform, F: Platform,
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static, L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static, F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
{ {
let leader_nodes = NodePool::<L::Blockchain>::new(args)?; let leader_nodes = NodePool::<L::Blockchain>::new(args, additional_callers)?;
let follower_nodes = NodePool::<F::Blockchain>::new(args)?; let follower_nodes = NodePool::<F::Blockchain>::new(args, additional_callers)?;
tests.par_iter().for_each( tests.par_iter().for_each(
|MetadataFile { |MetadataFile {
@@ -141,13 +159,18 @@ where
Ok(()) Ok(())
} }
fn execute_corpus(args: &Arguments, tests: &[MetadataFile], span: Span) -> anyhow::Result<()> { fn execute_corpus(
args: &Arguments,
tests: &[MetadataFile],
additional_callers: &[Address],
span: Span,
) -> anyhow::Result<()> {
match (&args.leader, &args.follower) { match (&args.leader, &args.follower) {
(TestingPlatform::Geth, TestingPlatform::Kitchensink) => { (TestingPlatform::Geth, TestingPlatform::Kitchensink) => {
run_driver::<Geth, Kitchensink>(args, tests, span)? run_driver::<Geth, Kitchensink>(args, tests, additional_callers, span)?
} }
(TestingPlatform::Geth, TestingPlatform::Geth) => { (TestingPlatform::Geth, TestingPlatform::Geth) => {
run_driver::<Geth, Geth>(args, tests, span)? run_driver::<Geth, Geth>(args, tests, additional_callers, span)?
} }
_ => unimplemented!(), _ => unimplemented!(),
} }
+30 -1
View File
@@ -1,6 +1,10 @@
use serde::Deserialize; use serde::Deserialize;
use crate::{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,4 +13,29 @@ 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>,
}
define_wrapper_type!(
/// A wrapper type for the index of test cases found in metadata file.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
CaseIdx(usize);
);
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 {
input.expected = self.expected.clone();
input
} else {
input
}
})
}
} }
+243 -112
View File
@@ -7,21 +7,24 @@ use alloy::{
primitives::{Address, Bytes, U256}, primitives::{Address, Bytes, U256},
rpc::types::TransactionRequest, rpc::types::TransactionRequest,
}; };
use alloy_primitives::B256;
use semver::VersionReq; use semver::VersionReq;
use serde::Deserialize; use serde::Deserialize;
use serde_json::Value;
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
use crate::metadata::ContractInstance;
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
pub struct Input { pub struct Input {
#[serde(default = "default_caller")] #[serde(default = "default_caller")]
pub caller: Address, pub caller: Address,
pub comment: Option<String>, pub comment: Option<String>,
#[serde(default = "default_instance")] #[serde(default = "default_instance")]
pub instance: String, pub instance: ContractInstance,
pub method: Method, pub method: Method,
pub calldata: Option<Calldata>, #[serde(default)]
pub calldata: Calldata,
pub expected: Option<Expected>, pub expected: Option<Expected>,
pub value: Option<String>, pub value: Option<String>,
pub storage: Option<HashMap<String, Calldata>>, pub storage: Option<HashMap<String, Calldata>>,
@@ -37,10 +40,18 @@ 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<B256>,
pub values: Calldata,
} }
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
@@ -71,109 +82,175 @@ pub enum Method {
FunctionName(String), FunctionName(String),
} }
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 {
fn default() -> Self {
Self::Compound(Default::default())
}
}
impl Calldata {
pub fn find_all_contract_instances(&self, vec: &mut Vec<ContractInstance>) {
if let Calldata::Compound(compound) = self {
for item in compound {
if let Some(instance) = item.strip_suffix(".address") {
vec.push(ContractInstance::new_from(instance))
}
}
}
}
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,
buffer: &mut Vec<u8>,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<()> {
match self {
Calldata::Single(string) => {
alloy::hex::decode_to_slice(string, buffer)?;
}
Calldata::Compound(items) => {
for (arg_idx, arg) in items.iter().enumerate() {
match resolve_argument(arg, deployed_contracts, chain_state_provider) {
Ok(resolved) => {
buffer.extend(resolved.to_be_bytes::<32>());
}
Err(error) => {
tracing::error!(arg, arg_idx, ?error, "Failed to resolve argument");
return Err(error);
}
};
}
}
};
Ok(())
}
pub fn size_requirement(&self) -> usize {
match self {
Calldata::Single(single) => (single.len() - 2) / 2,
Calldata::Compound(items) => items.len() * 32,
}
}
}
impl Input { impl Input {
fn instance_to_address( fn instance_to_address(
&self, &self,
instance: &str, instance: &ContractInstance,
deployed_contracts: &HashMap<String, Address>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
) -> anyhow::Result<Address> { ) -> anyhow::Result<Address> {
deployed_contracts deployed_contracts
.get(instance) .get(instance)
.copied() .map(|(a, _)| *a)
.ok_or_else(|| anyhow::anyhow!("instance {instance} not deployed")) .ok_or_else(|| anyhow::anyhow!("instance {instance:?} not deployed"))
} }
pub fn encoded_input( pub fn encoded_input(
&self, &self,
deployed_abis: &HashMap<String, JsonAbi>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
deployed_contracts: &HashMap<String, Address>,
chain_state_provider: &impl EthereumNode, chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<Bytes> { ) -> anyhow::Result<Bytes> {
let Method::FunctionName(ref function_name) = self.method else { match self.method {
return Ok(Bytes::default()); // fallback or deployer — no input Method::Deployer | Method::Fallback => {
}; let calldata = self
.calldata
.calldata(deployed_contracts, chain_state_provider)?;
let Some(abi) = deployed_abis.get(&self.instance) else { Ok(calldata.into())
tracing::error!( }
contract_name = self.instance, Method::FunctionName(ref function_name) => {
available_abis = ?deployed_abis.keys().collect::<Vec<_>>(), let Some(abi) = deployed_contracts.get(&self.instance).map(|(_, a)| a) else {
"Attempted to lookup ABI of contract but it wasn't found" tracing::error!(
); contract_name = self.instance.as_ref(),
anyhow::bail!("ABI for instance '{}' not found", &self.instance); available_abis = ?deployed_contracts.keys().collect::<Vec<_>>(),
}; "Attempted to lookup ABI of contract but it wasn't found"
);
anyhow::bail!("ABI for instance '{}' not found", self.instance.as_ref());
};
tracing::trace!("ABI found for instance: {}", &self.instance); tracing::trace!("ABI found for instance: {}", &self.instance.as_ref());
// We follow the same logic that's implemented in the matter-labs-tester where they resolve // We follow the same logic that's implemented in the matter-labs-tester where they resolve
// the function name into a function selector and they assume that he function doesn't have // the function name into a function selector and they assume that he function doesn't have
// any existing overloads. // any existing overloads.
// https://github.com/matter-labs/era-compiler-tester/blob/1dfa7d07cba0734ca97e24704f12dd57f6990c2c/compiler_tester/src/test/case/input/mod.rs#L158-L190 // https://github.com/matter-labs/era-compiler-tester/blob/1dfa7d07cba0734ca97e24704f12dd57f6990c2c/compiler_tester/src/test/case/input/mod.rs#L158-L190
let function = abi let function = abi
.functions() .functions()
.find(|function| function.name.starts_with(function_name)) .find(|function| function.signature().starts_with(function_name))
.ok_or_else(|| { .ok_or_else(|| {
anyhow::anyhow!( anyhow::anyhow!(
"Function with name {:?} not found in ABI for the instance {:?}", "Function with name {:?} not found in ABI for the instance {:?}",
function_name, function_name,
&self.instance &self.instance
) )
})?; })?;
tracing::trace!("Functions found for instance: {}", &self.instance); tracing::trace!("Functions found for instance: {}", self.instance.as_ref());
let calldata_args = match &self.calldata { tracing::trace!(
Some(Calldata::Compound(args)) => args, "Starting encoding ABI's parameters for instance: {}",
_ => anyhow::bail!("Expected compound calldata for function call"), self.instance.as_ref()
}; );
if calldata_args.len() != function.inputs.len() { // Allocating a vector that we will be using for the calldata. The vector size will be:
anyhow::bail!( // 4 bytes for the function selector.
"Function expects {} args, but got {}", // function.inputs.len() * 32 bytes for the arguments (each argument is a U256).
function.inputs.len(), //
calldata_args.len() // We're using indices in the following code in order to avoid the need for us to allocate
); // a new buffer for each one of the resolved arguments.
let mut calldata = Vec::<u8>::with_capacity(4 + self.calldata.size_requirement());
calldata.extend(function.selector().0);
self.calldata.calldata_into_slice(
&mut calldata,
deployed_contracts,
chain_state_provider,
)?;
Ok(calldata.into())
}
} }
tracing::trace!(
"Starting encoding ABI's parameters for instance: {}",
&self.instance
);
// Allocating a vector that we will be using for the calldata. The vector size will be:
// 4 bytes for the function selector.
// function.inputs.len() * 32 bytes for the arguments (each argument is a U256).
//
// We're using indices in the following code in order to avoid the need for us to allocate
// a new buffer for each one of the resolved arguments.
let mut calldata = Vec::<u8>::with_capacity(4 + calldata_args.len() * 32);
calldata.extend(function.selector().0);
for (arg_idx, arg) in calldata_args.iter().enumerate() {
match resolve_argument(arg, deployed_contracts, chain_state_provider) {
Ok(resolved) => {
calldata.extend(resolved.to_be_bytes::<32>());
}
Err(error) => {
tracing::error!(arg, arg_idx, ?error, "Failed to resolve argument");
return Err(error);
}
};
}
Ok(calldata.into())
} }
/// Parse this input into a legacy transaction. /// Parse this input into a legacy transaction.
pub fn legacy_transaction( pub fn legacy_transaction(
&self, &self,
nonce: u64, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
deployed_contracts: &HashMap<String, Address>,
deployed_abis: &HashMap<String, JsonAbi>,
chain_state_provider: &impl EthereumNode, chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<TransactionRequest> { ) -> anyhow::Result<TransactionRequest> {
let input_data = let input_data = self.encoded_input(deployed_contracts, chain_state_provider)?;
self.encoded_input(deployed_abis, deployed_contracts, chain_state_provider)?; let transaction_request = TransactionRequest::default();
let transaction_request = TransactionRequest::default().nonce(nonce);
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
@@ -181,13 +258,22 @@ impl Input {
.input(input_data.into())), .input(input_data.into())),
} }
} }
pub fn find_all_contract_instances(&self) -> Vec<ContractInstance> {
let mut vec = Vec::new();
vec.push(self.instance.clone());
self.calldata.find_all_contract_instances(&mut vec);
vec
}
} }
fn default_instance() -> String { pub fn default_instance() -> ContractInstance {
"Test".to_string() ContractInstance::new_from("Test")
} }
fn default_caller() -> Address { pub fn default_caller() -> Address {
"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1".parse().unwrap() "90F8bf6A479f320ead074411a4B0e7944Ea8c9C1".parse().unwrap()
} }
@@ -201,13 +287,14 @@ fn default_caller() -> Address {
/// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146 /// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146
fn resolve_argument( fn resolve_argument(
value: &str, value: &str,
deployed_contracts: &HashMap<String, Address>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
chain_state_provider: &impl EthereumNode, chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<U256> { ) -> anyhow::Result<U256> {
if let Some(instance) = value.strip_suffix(".address") { if let Some(instance) = value.strip_suffix(".address") {
Ok(U256::from_be_slice( Ok(U256::from_be_slice(
deployed_contracts deployed_contracts
.get(instance) .get(&ContractInstance::new_from(instance))
.map(|(a, _)| *a)
.ok_or_else(|| anyhow::anyhow!("Instance `{}` not found", instance))? .ok_or_else(|| anyhow::anyhow!("Instance `{}` not found", instance))?
.as_ref(), .as_ref(),
)) ))
@@ -282,22 +369,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)
} }
@@ -357,19 +441,19 @@ mod tests {
.0; .0;
let input = Input { let input = Input {
instance: "Contract".to_string(), instance: ContractInstance::new_from("Contract"),
method: Method::FunctionName("store".to_owned()), method: Method::FunctionName("store".to_owned()),
calldata: Some(Calldata::Compound(vec!["42".into()])), calldata: Calldata::Compound(vec!["42".into()]),
..Default::default() ..Default::default()
}; };
let mut deployed_abis = HashMap::new(); let mut contracts = HashMap::new();
deployed_abis.insert("Contract".to_string(), parsed_abi); contracts.insert(
let deployed_contracts = HashMap::new(); ContractInstance::new_from("Contract"),
(Address::ZERO, parsed_abi),
);
let encoded = input let encoded = input.encoded_input(&contracts, &DummyEthereumNode).unwrap();
.encoded_input(&deployed_abis, &deployed_contracts, &DummyEthereumNode)
.unwrap();
assert!(encoded.0.starts_with(&selector)); assert!(encoded.0.starts_with(&selector));
type T = (u64,); type T = (u64,);
@@ -399,21 +483,68 @@ mod tests {
.0; .0;
let input: Input = Input { let input: Input = Input {
instance: "Contract".to_string(), instance: ContractInstance::new_from("Contract"),
method: Method::FunctionName("send".to_owned()), method: Method::FunctionName("send".to_owned()),
calldata: Some(Calldata::Compound(vec![ calldata: Calldata::Compound(vec![
"0x1000000000000000000000000000000000000001".to_string(), "0x1000000000000000000000000000000000000001".to_string(),
])), ]),
..Default::default() ..Default::default()
}; };
let mut abis = HashMap::new(); let mut contracts = HashMap::new();
abis.insert("Contract".to_string(), parsed_abi); contracts.insert(
let contracts = HashMap::new(); ContractInstance::new_from("Contract"),
(Address::ZERO, parsed_abi),
);
let encoded = input let encoded = input.encoded_input(&contracts, &DummyEthereumNode).unwrap();
.encoded_input(&abis, &contracts, &DummyEthereumNode) assert!(encoded.0.starts_with(&selector));
.unwrap();
type T = (alloy_primitives::Address,);
let decoded: T = T::abi_decode(&encoded.0[4..]).unwrap();
assert_eq!(
decoded.0,
address!("0x1000000000000000000000000000000000000001")
);
}
#[test]
fn test_encoded_input_address_with_signature() {
let raw_abi = r#"[
{
"inputs": [{"name": "recipient", "type": "address"}],
"name": "send",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]"#;
let parsed_abi: JsonAbi = serde_json::from_str(raw_abi).unwrap();
let selector = parsed_abi
.function("send")
.unwrap()
.first()
.unwrap()
.selector()
.0;
let input: Input = Input {
instance: ContractInstance::new_from("Contract"),
method: Method::FunctionName("send(address)".to_owned()),
calldata: Calldata::Compound(vec![
"0x1000000000000000000000000000000000000001".to_string(),
]),
..Default::default()
};
let mut contracts = HashMap::new();
contracts.insert(
ContractInstance::new_from("Contract"),
(Address::ZERO, parsed_abi),
);
let encoded = input.encoded_input(&contracts, &DummyEthereumNode).unwrap();
assert!(encoded.0.starts_with(&selector)); assert!(encoded.0.starts_with(&selector));
type T = (alloy_primitives::Address,); type T = (alloy_primitives::Address,);
+1
View File
@@ -3,5 +3,6 @@
pub mod case; pub mod case;
pub mod corpus; pub mod corpus;
pub mod input; pub mod input;
pub mod macros;
pub mod metadata; pub mod metadata;
pub mod mode; pub mod mode;
+106
View File
@@ -0,0 +1,106 @@
/// Defines wrappers around types.
///
/// For example, the macro invocation seen below:
///
/// ```rust,ignore
/// define_wrapper_type!(CaseId => usize);
/// ```
///
/// Would define a wrapper type that looks like the following:
///
/// ```rust,ignore
/// pub struct CaseId(usize);
/// ```
///
/// And would also implement a number of methods on this type making it easier
/// to use.
///
/// These wrapper types become very useful as they make the code a lot easier
/// to read.
///
/// Take the following as an example:
///
/// ```rust,ignore
/// struct State {
/// contracts: HashMap<usize, HashMap<String, Vec<u8>>>
/// }
/// ```
///
/// In the above code it's hard to understand what the various types refer to or
/// what to expect them to contain.
///
/// With these wrapper types we're able to create code that's self-documenting
/// in that the types tell us what the code is referring to. The above code is
/// transformed into
///
/// ```rust,ignore
/// struct State {
/// contracts: HashMap<CaseId, HashMap<ContractName, ContractByteCode>>
/// }
/// ```
#[macro_export]
macro_rules! define_wrapper_type {
(
$(#[$meta: meta])*
$ident: ident($ty: ty) $(;)?
) => {
$(#[$meta])*
pub struct $ident($ty);
impl $ident {
pub fn new(value: $ty) -> Self {
Self(value)
}
pub fn new_from<T: Into<$ty>>(value: T) -> Self {
Self(value.into())
}
pub fn into_inner(self) -> $ty {
self.0
}
pub fn as_inner(&self) -> &$ty {
&self.0
}
}
impl AsRef<$ty> for $ident {
fn as_ref(&self) -> &$ty {
&self.0
}
}
impl AsMut<$ty> for $ident {
fn as_mut(&mut self) -> &mut $ty {
&mut self.0
}
}
impl std::ops::Deref for $ident {
type Target = $ty;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for $ident {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<$ty> for $ident {
fn from(value: $ty) -> Self {
Self(value)
}
}
impl From<$ident> for $ty {
fn from(value: $ident) -> Self {
value.0
}
}
};
}
+174 -23
View File
@@ -1,14 +1,17 @@
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
fmt::Display,
fs::{File, read_to_string}, fs::{File, read_to_string},
ops::Deref, ops::Deref,
path::{Path, PathBuf}, path::{Path, PathBuf},
str::FromStr,
}; };
use serde::Deserialize; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
case::Case, case::Case,
define_wrapper_type,
mode::{Mode, SolcMode}, mode::{Mode, SolcMode},
}; };
@@ -42,7 +45,8 @@ 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 cases: Vec<Case>, pub cases: Vec<Case>,
pub contracts: Option<BTreeMap<String, String>>, pub contracts: Option<BTreeMap<ContractInstance, ContractPathAndIdentifier>>,
// TODO: Convert into wrapper types for clarity.
pub libraries: Option<BTreeMap<String, BTreeMap<String, String>>>, pub libraries: Option<BTreeMap<String, BTreeMap<String, String>>>,
pub ignore: Option<bool>, pub ignore: Option<bool>,
pub modes: Option<Vec<Mode>>, pub modes: Option<Vec<Mode>>,
@@ -77,28 +81,35 @@ impl Metadata {
.to_path_buf()) .to_path_buf())
} }
/// Extract the contract sources. /// Returns the contract sources with canonicalized paths for the files
/// pub fn contract_sources(
/// Returns a mapping of contract IDs to their source path and contract name. &self,
pub fn contract_sources(&self) -> anyhow::Result<BTreeMap<String, (PathBuf, String)>> { ) -> anyhow::Result<BTreeMap<ContractInstance, ContractPathAndIdentifier>> {
let directory = self.directory()?; let directory = self.directory()?;
let mut sources = BTreeMap::new(); let mut sources = BTreeMap::new();
let Some(contracts) = &self.contracts else { let Some(contracts) = &self.contracts else {
return Ok(sources); return Ok(sources);
}; };
for (id, contract) in contracts { for (
// TODO: broken if a colon is in the dir name.. alias,
let mut parts = contract.split(':'); ContractPathAndIdentifier {
let (Some(file_name), Some(contract_name)) = (parts.next(), parts.next()) else { contract_source_path,
anyhow::bail!("metadata contains invalid contract: {contract}"); contract_ident,
}; },
let file = directory.to_path_buf().join(file_name); ) in contracts
if !file.is_file() { {
anyhow::bail!("contract {id} is not a file: {}", file.display()); let alias = alias.clone();
} let absolute_path = directory.join(contract_source_path).canonicalize()?;
let contract_ident = contract_ident.clone();
sources.insert(id.clone(), (file, contract_name.to_string())); sources.insert(
alias,
ContractPathAndIdentifier {
contract_source_path: absolute_path,
contract_ident,
},
);
} }
Ok(sources) Ok(sources)
@@ -178,12 +189,16 @@ impl Metadata {
match serde_json::from_str::<Self>(&spec) { match serde_json::from_str::<Self>(&spec) {
Ok(mut metadata) => { Ok(mut metadata) => {
metadata.file_path = Some(path.to_path_buf()); metadata.file_path = Some(path.to_path_buf());
let name = path metadata.contracts = Some(
.file_name() [(
.expect("this should be the path to a Solidity file") ContractInstance::new_from("test"),
.to_str() ContractPathAndIdentifier {
.expect("the file name should be valid UTF-8k"); contract_source_path: path.to_path_buf(),
metadata.contracts = Some([(String::from("Test"), format!("{name}:Test"))].into()); contract_ident: ContractIdent::new_from("Test"),
},
)]
.into(),
);
Some(metadata) Some(metadata)
} }
Err(error) => { Err(error) => {
@@ -196,3 +211,139 @@ impl Metadata {
} }
} }
} }
define_wrapper_type!(
/// Represents a contract instance found a metadata file.
///
/// Typically, this is used as the key to the "contracts" field of metadata files.
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
ContractInstance(String);
);
define_wrapper_type!(
/// Represents a contract identifier found a metadata file.
///
/// A contract identifier is the name of the contract in the source code.
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
ContractIdent(String);
);
/// Represents an identifier used for contracts.
///
/// The type supports serialization from and into the following string format:
///
/// ```text
/// ${path}:${contract_ident}
/// ```
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
pub struct ContractPathAndIdentifier {
/// The path of the contract source code relative to the directory containing the metadata file.
pub contract_source_path: PathBuf,
/// The identifier of the contract.
pub contract_ident: ContractIdent,
}
impl Display for ContractPathAndIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}",
self.contract_source_path.display(),
self.contract_ident.as_ref()
)
}
}
impl FromStr for ContractPathAndIdentifier {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut splitted_string = s.split(":").peekable();
let mut path = None::<String>;
let mut identifier = None::<String>;
loop {
let Some(next_item) = splitted_string.next() else {
break;
};
if splitted_string.peek().is_some() {
match path {
Some(ref mut path) => {
path.push(':');
path.push_str(next_item);
}
None => path = Some(next_item.to_owned()),
}
} else {
identifier = Some(next_item.to_owned())
}
}
let Some(path) = path else {
anyhow::bail!("Path is not defined");
};
let Some(identifier) = identifier else {
anyhow::bail!("Contract identifier is not defined")
};
Ok(Self {
contract_source_path: PathBuf::from(path),
contract_ident: ContractIdent::new(identifier),
})
}
}
impl TryFrom<String> for ContractPathAndIdentifier {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::from_str(&value)
}
}
impl From<ContractPathAndIdentifier> for String {
fn from(value: ContractPathAndIdentifier) -> Self {
value.to_string()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn contract_identifier_respects_roundtrip_property() {
// Arrange
let string = "ERC20/ERC20.sol:ERC20";
// Act
let identifier = ContractPathAndIdentifier::from_str(string);
// Assert
let identifier = identifier.expect("Failed to parse");
assert_eq!(
identifier.contract_source_path.display().to_string(),
"ERC20/ERC20.sol"
);
assert_eq!(identifier.contract_ident, "ERC20".to_owned().into());
// Act
let reserialized = identifier.to_string();
// Assert
assert_eq!(string, reserialized);
}
#[test]
fn complex_metadata_file_can_be_deserialized() {
// Arrange
const JSON: &str = include_str!("../../../assets/test_metadata.json");
// Act
let metadata = serde_json::from_str::<Metadata>(JSON);
// Assert
metadata.expect("Failed to deserialize metadata");
}
}
+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>;
+108
View File
@@ -0,0 +1,108 @@
use ::core::pin::Pin;
use alloy::{
consensus::SignableTransaction,
network::{Network, TransactionBuilder, TxSigner},
primitives::Address,
providers::{
Provider, SendableTx,
fillers::{GasFiller, TxFiller},
},
signers::{Signature, local::PrivateKeySigner},
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)
}
}
}
/// This is a signer that is able to sign transactions for a specific address with another private
/// key.
pub struct AddressSigner {
pub private_key: PrivateKeySigner,
pub address: Address,
}
impl TxSigner<Signature> for AddressSigner {
fn address(&self) -> Address {
self.address
}
fn sign_transaction<'a, 'b, 'c>(
&'a self,
tx: &'b mut dyn SignableTransaction<Signature>,
) -> Pin<Box<dyn Future<Output = Result<Signature, alloy::signers::Error>> + Send + 'c>>
where
'a: 'c,
'b: 'c,
Self: 'c,
{
<PrivateKeySigner as TxSigner<Signature>>::sign_transaction(&self.private_key, tx)
}
}
+60 -47
View File
@@ -1,37 +1,38 @@
//! 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,
genesis::Genesis,
network::{Ethereum, EthereumWallet}, network::{Ethereum, EthereumWallet},
primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, U256}, primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, 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::{AddressSigner, FallbackGasFiller},
};
static NODE_COUNT: AtomicU32 = AtomicU32::new(0); static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
@@ -54,7 +55,10 @@ pub struct Instance {
network_id: u64, network_id: u64,
start_timeout: u64, start_timeout: u64,
wallet: EthereumWallet, wallet: EthereumWallet,
nonces: Mutex<HashMap<Address, u64>>, private_key: PrivateKeySigner,
nonce_manager: CachedNonceManager,
additional_callers: Vec<Address>,
/// 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 +86,24 @@ 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)?;
// Modifying the genesis file so that we get our private key to control the other accounts.
let mut genesis = serde_json::from_str::<Genesis>(&genesis)?;
for additional_caller in self.additional_callers.iter() {
let account = genesis.alloc.entry(*additional_caller).or_default();
account.private_key = Some(self.private_key.to_bytes());
*account = account
.clone()
.with_balance("1000000000000000000".parse().expect("Can't fail"));
}
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)?;
for additional_caller in self.additional_callers.iter() {
self.wallet.register_signer(AddressSigner {
private_key: self.private_key.clone(),
address: *additional_caller,
});
}
let mut child = Command::new(&self.geth) let mut child = Command::new(&self.geth)
.arg("init") .arg("init")
@@ -206,8 +226,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 +255,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 +336,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 +365,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();
@@ -437,7 +448,7 @@ impl EthereumNode for Instance {
} }
impl Node for Instance { impl Node for Instance {
fn new(config: &Arguments) -> Self { fn new(config: &Arguments, additional_callers: &[Address]) -> Self {
let geth_directory = config.directory().join(Self::BASE_DIRECTORY); let geth_directory = config.directory().join(Self::BASE_DIRECTORY);
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());
@@ -453,10 +464,12 @@ impl Node for Instance {
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: config.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(),
additional_callers: additional_callers.to_vec(),
private_key: config.signer(),
} }
} }
@@ -533,7 +546,7 @@ mod tests {
fn new_node() -> (Instance, TempDir) { fn new_node() -> (Instance, TempDir) {
let (args, temp_dir) = test_config(); let (args, temp_dir) = test_config();
let mut node = Instance::new(&args); let mut node = Instance::new(&args, &[]);
node.init(GENESIS_JSON.to_owned()) node.init(GENESIS_JSON.to_owned())
.expect("Failed to initialize the node") .expect("Failed to initialize the node")
.spawn_process() .spawn_process()
@@ -543,21 +556,21 @@ mod tests {
#[test] #[test]
fn init_works() { fn init_works() {
Instance::new(&test_config().0) Instance::new(&test_config().0, &[])
.init(GENESIS_JSON.to_string()) .init(GENESIS_JSON.to_string())
.unwrap(); .unwrap();
} }
#[test] #[test]
fn spawn_works() { fn spawn_works() {
Instance::new(&test_config().0) Instance::new(&test_config().0, &[])
.spawn(GENESIS_JSON.to_string()) .spawn(GENESIS_JSON.to_string())
.unwrap(); .unwrap();
} }
#[test] #[test]
fn version_works() { fn version_works() {
let version = Instance::new(&test_config().0).version().unwrap(); let version = Instance::new(&test_config().0, &[]).version().unwrap();
assert!( assert!(
version.starts_with("geth version"), version.starts_with("geth version"),
"expected version string, got: '{version}'" "expected version string, got: '{version}'"
+48 -51
View File
@@ -1,13 +1,9 @@
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,
}; };
@@ -23,7 +19,7 @@ use alloy::{
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,
@@ -40,7 +36,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};
static NODE_COUNT: AtomicU32 = AtomicU32::new(0); static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
@@ -55,7 +51,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
@@ -350,9 +346,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 +395,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 +424,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();
@@ -513,7 +507,7 @@ impl EthereumNode for KitchensinkNode {
} }
impl Node for KitchensinkNode { impl Node for KitchensinkNode {
fn new(config: &Arguments) -> Self { fn new(config: &Arguments, _additional_callers: &[Address]) -> Self {
let kitchensink_directory = config.directory().join(Self::BASE_DIRECTORY); let kitchensink_directory = config.directory().join(Self::BASE_DIRECTORY);
let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst); let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst);
let base_directory = kitchensink_directory.join(id.to_string()); let base_directory = kitchensink_directory.join(id.to_string());
@@ -529,7 +523,7 @@ impl Node for KitchensinkNode {
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),
@@ -820,6 +814,12 @@ impl TransactionBuilder<KitchenSinkNetwork> for <Ethereum as Network>::Transacti
> { > {
Ok(wallet.sign_request(self).await?) Ok(wallet.sign_request(self).await?)
} }
fn take_nonce(&mut self) -> Option<u64> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::take_nonce(
self,
)
}
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
@@ -1020,7 +1020,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;
@@ -1060,7 +1060,7 @@ mod tests {
let _guard = NODE_START_MUTEX.lock().unwrap(); let _guard = NODE_START_MUTEX.lock().unwrap();
let (args, temp_dir) = test_config(); let (args, temp_dir) = test_config();
let mut node = KitchensinkNode::new(&args); let mut node = KitchensinkNode::new(&args, &[]);
node.init(GENESIS_JSON) node.init(GENESIS_JSON)
.expect("Failed to initialize the node") .expect("Failed to initialize the node")
.spawn_process() .spawn_process()
@@ -1115,7 +1115,7 @@ mod tests {
} }
"#; "#;
let mut dummy_node = KitchensinkNode::new(&test_config().0); let mut dummy_node = KitchensinkNode::new(&test_config().0, &[]);
// Call `init()` // Call `init()`
dummy_node.init(genesis_content).expect("init failed"); dummy_node.init(genesis_content).expect("init failed");
@@ -1159,7 +1159,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(genesis_json)
@@ -1232,7 +1232,7 @@ mod tests {
fn spawn_works() { fn spawn_works() {
let (config, _temp_dir) = test_config(); let (config, _temp_dir) = test_config();
let mut node = KitchensinkNode::new(&config); let mut node = KitchensinkNode::new(&config, &[]);
node.spawn(GENESIS_JSON.to_string()).unwrap(); node.spawn(GENESIS_JSON.to_string()).unwrap();
} }
@@ -1240,7 +1240,7 @@ mod tests {
fn version_works() { fn version_works() {
let (config, _temp_dir) = test_config(); let (config, _temp_dir) = test_config();
let node = KitchensinkNode::new(&config); let node = KitchensinkNode::new(&config, &[]);
let version = node.version().unwrap(); let version = node.version().unwrap();
assert!( assert!(
@@ -1253,7 +1253,7 @@ mod tests {
fn eth_rpc_version_works() { fn eth_rpc_version_works() {
let (config, _temp_dir) = test_config(); let (config, _temp_dir) = test_config();
let node = KitchensinkNode::new(&config); let node = KitchensinkNode::new(&config, &[]);
let version = node.eth_rpc_version().unwrap(); let version = node.eth_rpc_version().unwrap();
assert!( assert!(
@@ -1296,8 +1296,7 @@ mod tests {
let coinbase = node.block_coinbase(BlockNumberOrTag::Latest); let coinbase = node.block_coinbase(BlockNumberOrTag::Latest);
// Assert // Assert
let coinbase = coinbase.expect("Failed to get the coinbase"); let _ = coinbase.expect("Failed to get the coinbase");
assert_eq!(coinbase, Address::ZERO)
} }
#[test] #[test]
@@ -1309,8 +1308,7 @@ mod tests {
let block_difficulty = node.block_difficulty(BlockNumberOrTag::Latest); let block_difficulty = node.block_difficulty(BlockNumberOrTag::Latest);
// Assert // Assert
let block_difficulty = block_difficulty.expect("Failed to get the block difficulty"); let _ = block_difficulty.expect("Failed to get the block difficulty");
assert_eq!(block_difficulty, U256::ZERO)
} }
#[test] #[test]
@@ -1346,7 +1344,6 @@ mod tests {
let block_number = node.last_block_number(); let block_number = node.last_block_number();
// Assert // Assert
let block_number = block_number.expect("Failed to get the block number"); let _ = block_number.expect("Failed to get the block number");
assert_eq!(block_number, 0)
} }
} }
+3 -1
View File
@@ -1,8 +1,10 @@
//! This crate implements the testing nodes. //! This crate implements the testing nodes.
use alloy::primitives::Address;
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 geth; pub mod geth;
pub mod kitchensink; pub mod kitchensink;
pub mod pool; pub mod pool;
@@ -13,7 +15,7 @@ pub const GENESIS_JSON: &str = include_str!("../../../genesis.json");
/// An abstract interface for testing nodes. /// An abstract interface for testing nodes.
pub trait Node: EthereumNode { pub trait Node: EthereumNode {
/// Create a new uninitialized instance. /// Create a new uninitialized instance.
fn new(config: &Arguments) -> Self; fn new(config: &Arguments, additional_callers: &[Address]) -> Self;
/// Spawns a node configured according to the genesis json. /// Spawns a node configured according to the genesis json.
/// ///
+12 -4
View File
@@ -6,6 +6,7 @@ use std::{
thread, thread,
}; };
use alloy::primitives::Address;
use anyhow::Context; use anyhow::Context;
use revive_dt_config::Arguments; use revive_dt_config::Arguments;
@@ -23,7 +24,7 @@ where
T: Node + Send + 'static, T: Node + Send + 'static,
{ {
/// Create a new Pool. This will start as many nodes as there are workers in `config`. /// Create a new Pool. This will start as many nodes as there are workers in `config`.
pub fn new(config: &Arguments) -> anyhow::Result<Self> { pub fn new(config: &Arguments, additional_callers: &[Address]) -> anyhow::Result<Self> {
let nodes = config.workers; let nodes = config.workers;
let genesis = read_to_string(&config.genesis_file).context(format!( let genesis = read_to_string(&config.genesis_file).context(format!(
"can not read genesis file: {}", "can not read genesis file: {}",
@@ -34,7 +35,10 @@ where
for _ in 0..nodes { for _ in 0..nodes {
let config = config.clone(); let config = config.clone();
let genesis = genesis.clone(); let genesis = genesis.clone();
handles.push(thread::spawn(move || spawn_node::<T>(&config, genesis))); let additional_callers = additional_callers.to_vec();
handles.push(thread::spawn(move || {
spawn_node::<T>(&config, genesis, &additional_callers)
}));
} }
let mut nodes = Vec::with_capacity(nodes); let mut nodes = Vec::with_capacity(nodes);
@@ -60,8 +64,12 @@ where
} }
} }
fn spawn_node<T: Node + Send>(args: &Arguments, genesis: String) -> anyhow::Result<T> { fn spawn_node<T: Node + Send>(
let mut node = T::new(args); args: &Arguments,
genesis: String,
additional_callers: &[Address],
) -> anyhow::Result<T> {
let mut node = T::new(args, additional_callers);
tracing::info!("starting node: {}", node.connection_string()); tracing::info!("starting node: {}", node.connection_string());
node.spawn(genesis)?; node.spawn(genesis)?;
Ok(node) Ok(node)
+4 -16
View File
@@ -110,37 +110,25 @@ mod tests {
#[test] #[test]
fn try_get_windows() { fn try_get_windows() {
let version = List::download(List::WINDOWS_URL) let version = List::download(List::WINDOWS_URL).unwrap().latest_release;
.unwrap()
.latest_release
.into();
GHDownloader::windows(version).download().unwrap(); GHDownloader::windows(version).download().unwrap();
} }
#[test] #[test]
fn try_get_macosx() { fn try_get_macosx() {
let version = List::download(List::MACOSX_URL) let version = List::download(List::MACOSX_URL).unwrap().latest_release;
.unwrap()
.latest_release
.into();
GHDownloader::macosx(version).download().unwrap(); GHDownloader::macosx(version).download().unwrap();
} }
#[test] #[test]
fn try_get_linux() { fn try_get_linux() {
let version = List::download(List::LINUX_URL) let version = List::download(List::LINUX_URL).unwrap().latest_release;
.unwrap()
.latest_release
.into();
GHDownloader::linux(version).download().unwrap(); GHDownloader::linux(version).download().unwrap();
} }
#[test] #[test]
fn try_get_wasm() { fn try_get_wasm() {
let version = List::download(List::WASM_URL) let version = List::download(List::WASM_URL).unwrap().latest_release;
.unwrap()
.latest_release
.into();
GHDownloader::wasm(version).download().unwrap(); GHDownloader::wasm(version).download().unwrap();
} }
} }