Compare commits

..

41 Commits

Author SHA1 Message Date
Omar Abdulla d04cdf337f Merge remote-tracking branch 'origin/main' into cleanup-execution-logic 2025-07-18 14:47:22 +03:00
Omar Abdulla 11b568a442 Merge remote-tracking branch 'origin/main' into cleanup-execution-logic 2025-07-17 19:46:27 +03:00
Omar Abdulla 74b5e34260 Cleanup execution logic 2025-07-16 18:21:14 +03:00
Omar Abdulla 4c55bba53d Merge branch 'feature/better-input-parser' into omar-temp 2025-07-16 15:34:28 +03:00
Omar Abdulla 222b5d4f86 Merge branch 'bugfix/finding-contract-abi' into omar-temp 2025-07-16 15:34:21 +03:00
Omar Abdulla 762288bf04 Merge branch 'bugfix/kitchensink-gas-limit' into omar-temp 2025-07-16 15:34:04 +03:00
Omar Abdulla cac2220188 Merge remote-tracking branch 'origin/main' into feature/better-input-parser 2025-07-16 15:31:33 +03:00
Omar Abdulla 4bf22f2d2b Merge remote-tracking branch 'origin/main' into bugfix/finding-contract-abi 2025-07-16 15:21:21 +03:00
Omar Abdulla 5c64de7e67 Change kitchensink gas limit assertion 2025-07-15 14:08:55 +03:00
Omar Abdulla fa4bf95091 Add comment on alternative solutions 2025-07-15 13:57:31 +03:00
Omar Abdulla 2537a132e6 Fix tests 2025-07-15 13:54:58 +03:00
Omar Abdulla 20da99784e Add resolution logic for other matterlabs variables 2025-07-14 23:51:59 +03:00
Omar Abdulla 6d7cd67931 Expose APIs for getting the info of a specific block 2025-07-14 23:21:53 +03:00
Omar Abdulla 68bda92465 Add a way to get block info from the node 2025-07-14 23:14:37 +03:00
Omar Abdulla ddd775d703 Add a way to get the block difficulty from the node 2025-07-14 22:53:40 +03:00
Omar Abdulla 02547b62ee Add a way to get the coinbase address 2025-07-14 22:48:45 +03:00
Omar Abdulla 61540741e1 Add support for getting the gas limit from the node 2025-07-14 22:37:50 +03:00
Omar Abdulla fa4bbbb987 Use provider method in tests 2025-07-14 22:33:09 +03:00
Omar Abdulla 8f80b1da8a Get kitchensink provider to use kitchensink network 2025-07-14 22:30:30 +03:00
Omar Abdulla c6d63255ec Merge remote-tracking branch 'origin/bugfix/kitchensink-gas-limit' into feature/better-input-parser 2025-07-14 22:29:34 +03:00
Omar Abdulla a4f5c4c8af Add ability to get the chain_id from node 2025-07-14 22:16:37 +03:00
Omar Abdulla 7d48d1600e Give nodes a standard way to get their alloy provider 2025-07-14 21:59:44 +03:00
Omar Abdulla e7e00a50dd Merge branch 'bugfix/argument-encoding' into feature/better-input-parser 2025-07-14 21:37:09 +03:00
Omar Abdulla 27a0a0de0b Fix doc test 2025-07-14 21:33:57 +03:00
Omar Abdulla 83c20b1be3 Fix tests 2025-07-14 21:30:35 +03:00
Omar Abdulla 331705134a Update the async runtime with syntactic sugar. 2025-07-14 21:13:58 +03:00
Omar Abdulla 075c8235a7 Merge remote-tracking branch 'origin/main' into bugfix/argument-encoding 2025-07-14 20:36:42 +03:00
Omar Abdulla 5f86ade1e0 Implement ABI fix in the compiler trait impl 2025-07-14 20:31:06 +03:00
Omar Abdulla 43064022e8 Merge remote-tracking branch 'origin/main' into bugfix/finding-contract-abi 2025-07-14 20:25:08 +03:00
Omar Abdulla 57bb015fa3 Merge remote-tracking branch 'origin/main' into bugfix/kitchensink-gas-limit 2025-07-14 19:31:34 +03:00
Omar Abdulla 43e0d0e592 Remove reliance on the web3 crate 2025-07-14 18:27:38 +03:00
Omar 332012754d Merge pull request #35 from paritytech/bugfix/fix-kitchensink-no-advance
Fix an issue where kitchensink won't advance
2025-07-14 17:54:58 +03:00
Omar Abdulla eb6c64c17a Merge remote-tracking branch 'origin/main' into bugfix/kitchensink-gas-limit 2025-07-14 13:27:40 +03:00
Omar Abdulla 2373872230 Avoid extra buffer allocation 2025-07-14 00:02:48 +03:00
Omar Abdulla e3723e780a Fix function selector and argument encoding 2025-07-13 19:52:06 +03:00
Omar Abdulla 4d4398f83e Fix the ABI finding logic 2025-07-13 15:59:23 +03:00
Omar Abdulla 76c85f191c fix clippy warning 2025-07-11 17:31:42 +03:00
Omar Abdulla 7664e9735e fix clippy warning 2025-07-11 17:20:36 +03:00
Omar Abdulla 4bab457114 Added --dev to substrate-node arguments.
This commit adds the `--dev` argument to the `substrate-node` to allow
the chain to keep advancing as time goes own. We have found that if this
option is not added then the chain won't advance forward.
2025-07-11 17:18:42 +03:00
Omar Abdulla f6374ad52a fix formatting 2025-07-11 14:49:05 +03:00
Omar Abdulla abba0cee08 Introduce a custom kitchensink network 2025-07-11 11:26:55 +03:00
22 changed files with 797 additions and 2243 deletions
Generated
+76 -83
View File
@@ -67,9 +67,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "alloy"
version = "1.0.22"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ad4eb51e7845257b70c51b38ef8d842d5e5e93196701fcbd757577971a043c6"
checksum = "ae58d888221eecf621595e2096836ce7cfc37be06bfa39d7f64aa6a3ea4c9e5b"
dependencies = [
"alloy-consensus",
"alloy-contract",
@@ -102,16 +102,15 @@ dependencies = [
[[package]]
name = "alloy-consensus"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3b746060277f3d7f9c36903bb39b593a741cb7afcb0044164c28f0e9b673f0"
checksum = "ad451f9a70c341d951bca4e811d74dbe1e193897acd17e9dbac1353698cc430b"
dependencies = [
"alloy-eips",
"alloy-primitives",
"alloy-rlp",
"alloy-serde",
"alloy-trie",
"alloy-tx-macros",
"auto_impl",
"c-kzg",
"derive_more 2.0.1",
@@ -127,9 +126,9 @@ dependencies = [
[[package]]
name = "alloy-consensus-any"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf98679329fa708fa809ea596db6d974da892b068ad45e48ac1956f582edf946"
checksum = "142daffb15d5be1a2b20d2cd540edbcef03037b55d4ff69dc06beb4d06286dba"
dependencies = [
"alloy-consensus",
"alloy-eips",
@@ -141,9 +140,9 @@ dependencies = [
[[package]]
name = "alloy-contract"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a10e47f5305ea08c37b1772086c1573e9a0a257227143996841172d37d3831bb"
checksum = "ebf25443920ecb9728cb087fe4dc04a0b290bd6ac85638c58fe94aba70f1a44e"
dependencies = [
"alloy-consensus",
"alloy-dyn-abi",
@@ -158,7 +157,6 @@ dependencies = [
"alloy-transport",
"futures",
"futures-util",
"serde_json",
"thiserror 2.0.12",
]
@@ -229,9 +227,9 @@ dependencies = [
[[package]]
name = "alloy-eips"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f562a81278a3ed83290e68361f2d1c75d018ae3b8589a314faf9303883e18ec9"
checksum = "3056872f6da48046913e76edb5ddced272861f6032f09461aea1a2497be5ae5d"
dependencies = [
"alloy-eip2124",
"alloy-eip2930",
@@ -249,16 +247,15 @@ dependencies = [
[[package]]
name = "alloy-genesis"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc41384e9ab8c9b2fb387c52774d9d432656a28edcda1c2d4083e96051524518"
checksum = "c98fb40f07997529235cc474de814cd7bd9de561e101716289095696c0e4639d"
dependencies = [
"alloy-eips",
"alloy-primitives",
"alloy-serde",
"alloy-trie",
"serde",
"serde_with",
]
[[package]]
@@ -275,13 +272,12 @@ dependencies = [
[[package]]
name = "alloy-json-rpc"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12c454fcfcd5d26ed3b8cae5933cbee9da5f0b05df19b46d4bd4446d1f082565"
checksum = "dc08b31ebf9273839bd9a01f9333cbb7a3abb4e820c312ade349dd18bdc79581"
dependencies = [
"alloy-primitives",
"alloy-sol-types",
"http",
"serde",
"serde_json",
"thiserror 2.0.12",
@@ -290,9 +286,9 @@ dependencies = [
[[package]]
name = "alloy-network"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d6d39eabe5c7b3d8f23ac47b0b683b99faa4359797114636c66e0743103d05"
checksum = "ed117b08f0cc190312bf0c38c34cf4f0dabfb4ea8f330071c587cd7160a88cb2"
dependencies = [
"alloy-consensus",
"alloy-consensus-any",
@@ -316,9 +312,9 @@ dependencies = [
[[package]]
name = "alloy-network-primitives"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3704fa8b7ba9ba3f378d99b3d628c8bc8c2fc431b709947930f154e22a8368b6"
checksum = "c7162ff7be8649c0c391f4e248d1273e85c62076703a1f3ec7daf76b283d886d"
dependencies = [
"alloy-consensus",
"alloy-eips",
@@ -340,13 +336,13 @@ dependencies = [
"derive_more 2.0.1",
"foldhash",
"hashbrown 0.15.3",
"indexmap 2.10.0",
"indexmap 2.9.0",
"itoa",
"k256",
"keccak-asm",
"paste",
"proptest",
"rand 0.9.2",
"rand 0.9.1",
"ruint",
"rustc-hash",
"serde",
@@ -356,9 +352,9 @@ dependencies = [
[[package]]
name = "alloy-provider"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08800e8cbe70c19e2eb7cf3d7ff4b28bdd9b3933f8e1c8136c7d910617ba03bf"
checksum = "d84eba1fd8b6fe8b02f2acd5dd7033d0f179e304bd722d11e817db570d1fa6c4"
dependencies = [
"alloy-chains",
"alloy-consensus",
@@ -384,7 +380,6 @@ dependencies = [
"either",
"futures",
"futures-utils-wasm",
"http",
"lru",
"parking_lot",
"pin-project",
@@ -400,9 +395,9 @@ dependencies = [
[[package]]
name = "alloy-pubsub"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae68457a2c2ead6bd7d7acb5bf5f1623324b1962d4f8e7b0250657a3c3ab0a0b"
checksum = "8550f7306e0230fc835eb2ff4af0a96362db4b6fc3f25767d161e0ad0ac765bf"
dependencies = [
"alloy-json-rpc",
"alloy-primitives",
@@ -443,9 +438,9 @@ dependencies = [
[[package]]
name = "alloy-rpc-client"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162301b5a57d4d8f000bf30f4dcb82f9f468f3e5e846eeb8598dd39e7886932c"
checksum = "518a699422a3eab800f3dac2130d8f2edba8e4fff267b27a9c7dc6a2b0d313ee"
dependencies = [
"alloy-json-rpc",
"alloy-primitives",
@@ -453,6 +448,7 @@ dependencies = [
"alloy-transport",
"alloy-transport-http",
"alloy-transport-ipc",
"async-stream",
"futures",
"pin-project",
"reqwest",
@@ -462,15 +458,16 @@ dependencies = [
"tokio-stream",
"tower",
"tracing",
"tracing-futures",
"url",
"wasmtimer",
]
[[package]]
name = "alloy-rpc-types"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cd8ca94ae7e2b32cc3895d9981f3772aab0b4756aa60e9ed0bcfee50f0e1328"
checksum = "c000cab4ec26a4b3e29d144e999e1c539c2fa0abed871bf90311eb3466187ca8"
dependencies = [
"alloy-primitives",
"alloy-rpc-types-eth",
@@ -481,9 +478,9 @@ dependencies = [
[[package]]
name = "alloy-rpc-types-any"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "076b47e834b367d8618c52dd0a0d6a711ddf66154636df394805300af4923b8a"
checksum = "508b2fbe66d952089aa694e53802327798806498cd29ff88c75135770ecaabfc"
dependencies = [
"alloy-consensus-any",
"alloy-rpc-types-eth",
@@ -492,9 +489,9 @@ dependencies = [
[[package]]
name = "alloy-rpc-types-debug"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94a2a86ad7b7d718c15e79d0779bd255561b6b22968dc5ed2e7c0fbc43bb55fe"
checksum = "8c832f2e851801093928dbb4b7bd83cd22270faf76b2e080646b806a285c8757"
dependencies = [
"alloy-primitives",
"serde",
@@ -502,9 +499,9 @@ dependencies = [
[[package]]
name = "alloy-rpc-types-eth"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c2f847e635ec0be819d06e2ada4bcc4e4204026a83c4bfd78ae8d550e027ae7"
checksum = "fcaf7dff0fdd756a714d58014f4f8354a1706ebf9fa2cf73431e0aeec3c9431e"
dependencies = [
"alloy-consensus",
"alloy-consensus-any",
@@ -517,15 +514,14 @@ dependencies = [
"itertools 0.14.0",
"serde",
"serde_json",
"serde_with",
"thiserror 2.0.12",
]
[[package]]
name = "alloy-rpc-types-trace"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fc58180302a94c934d455eeedb3ecb99cdc93da1dbddcdbbdb79dd6fe618b2a"
checksum = "6e3507a04e868dd83219ad3cd6a8c58aefccb64d33f426b3934423a206343e84"
dependencies = [
"alloy-primitives",
"alloy-rpc-types-eth",
@@ -537,9 +533,9 @@ dependencies = [
[[package]]
name = "alloy-serde"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae699248d02ade9db493bbdae61822277dc14ae0f82a5a4153203b60e34422a6"
checksum = "730e8f2edf2fc224cabd1c25d090e1655fa6137b2e409f92e5eec735903f1507"
dependencies = [
"alloy-primitives",
"serde",
@@ -548,9 +544,9 @@ dependencies = [
[[package]]
name = "alloy-signer"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cf7d793c813515e2b627b19a15693960b3ed06670f9f66759396d06ebe5747b"
checksum = "6b0d2428445ec13edc711909e023d7779618504c4800be055a5b940025dbafe3"
dependencies = [
"alloy-primitives",
"async-trait",
@@ -563,9 +559,9 @@ dependencies = [
[[package]]
name = "alloy-signer-local"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51a424bc5a11df0d898ce0fd15906b88ebe2a6e4f17a514b51bc93946bb756bd"
checksum = "e14fe6fedb7fe6e0dfae47fe020684f1d8e063274ef14bca387ddb7a6efa8ec1"
dependencies = [
"alloy-consensus",
"alloy-network",
@@ -601,7 +597,7 @@ dependencies = [
"alloy-sol-macro-input",
"const-hex",
"heck",
"indexmap 2.10.0",
"indexmap 2.9.0",
"proc-macro-error2",
"proc-macro2",
"quote",
@@ -652,9 +648,9 @@ dependencies = [
[[package]]
name = "alloy-transport"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f317d20f047b3de4d9728c556e2e9a92c9a507702d2016424cd8be13a74ca5e"
checksum = "a712bdfeff42401a7dd9518f72f617574c36226a9b5414537fedc34350b73bf9"
dependencies = [
"alloy-json-rpc",
"alloy-primitives",
@@ -675,9 +671,9 @@ dependencies = [
[[package]]
name = "alloy-transport-http"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff084ac7b1f318c87b579d221f11b748341d68b9ddaa4ffca5e62ed2b8cfefb4"
checksum = "7ea5a76d7f2572174a382aedf36875bedf60bcc41116c9f031cf08040703a2dc"
dependencies = [
"alloy-json-rpc",
"alloy-transport",
@@ -690,9 +686,9 @@ dependencies = [
[[package]]
name = "alloy-transport-ipc"
version = "1.0.22"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb099cdad8ed2e6a80811cdf9bbf715ebf4e34c981b4a6e2d1f9daacbf8b218"
checksum = "606af17a7e064d219746f6d2625676122c79d78bf73dfe746d6db9ecd7dbcb85"
dependencies = [
"alloy-json-rpc",
"alloy-pubsub",
@@ -710,9 +706,9 @@ dependencies = [
[[package]]
name = "alloy-trie"
version = "0.9.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bada1fc392a33665de0dc50d401a3701b62583c655e3522a323490a5da016962"
checksum = "983d99aa81f586cef9dae38443245e585840fcf0fc58b09aee0b1f27aed1d500"
dependencies = [
"alloy-primitives",
"alloy-rlp",
@@ -724,19 +720,6 @@ dependencies = [
"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]]
name = "android-tzdata"
version = "0.1.1"
@@ -2417,7 +2400,7 @@ dependencies = [
"futures-core",
"futures-sink",
"http",
"indexmap 2.10.0",
"indexmap 2.9.0",
"slab",
"tokio",
"tokio-util",
@@ -2859,9 +2842,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.10.0"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
"hashbrown 0.15.3",
@@ -3285,14 +3268,13 @@ dependencies = [
[[package]]
name = "nybbles"
version = "0.4.1"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "675b3a54e5b12af997abc8b6638b0aee51a28caedab70d4967e0d5db3a3f1d06"
checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307"
dependencies = [
"alloy-rlp",
"cfg-if",
"const-hex",
"proptest",
"ruint",
"serde",
"smallvec",
]
@@ -3737,9 +3719,9 @@ dependencies = [
[[package]]
name = "rand"
version = "0.9.2"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
@@ -3980,7 +3962,6 @@ dependencies = [
"alloy",
"anyhow",
"clap",
"indexmap 2.10.0",
"rayon",
"revive-dt-compiler",
"revive-dt-config",
@@ -4131,7 +4112,7 @@ dependencies = [
"primitive-types 0.12.2",
"proptest",
"rand 0.8.5",
"rand 0.9.2",
"rand 0.9.1",
"rlp",
"ruint-macro",
"serde",
@@ -4526,7 +4507,7 @@ dependencies = [
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.10.0",
"indexmap 2.9.0",
"serde",
"serde_derive",
"serde_json",
@@ -5415,7 +5396,7 @@ version = "0.22.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
dependencies = [
"indexmap 2.10.0",
"indexmap 2.9.0",
"serde",
"serde_spanned",
"toml_datetime",
@@ -5507,6 +5488,18 @@ dependencies = [
"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]]
name = "tracing-log"
version = "0.2.0"
+1 -3
View File
@@ -51,7 +51,6 @@ tracing-subscriber = { version = "0.3.19", default-features = false, features =
"json",
"env-filter",
] }
indexmap = { version = "2.10.0", default-features = false }
# revive compiler
revive-solc-json-interface = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" }
@@ -59,7 +58,7 @@ revive-common = { git = "https://github.com/paritytech/revive", rev = "3389865af
revive-differential = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" }
[workspace.dependencies.alloy]
version = "1.0.22"
version = "1.0"
default-features = false
features = [
"json-abi",
@@ -73,7 +72,6 @@ features = [
"network",
"serde",
"rpc-types-eth",
"genesis",
]
[profile.bench]
-326
View File
@@ -1,326 +0,0 @@
{
"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"
}
-1
View File
@@ -23,7 +23,6 @@ revive-dt-report = { workspace = true }
alloy = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true }
indexmap = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
rayon = { workspace = true }
-73
View File
@@ -1,73 +0,0 @@
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
+3 -4
View File
@@ -1,21 +1,20 @@
//! The revive differential testing core library.
//!
//! This crate defines the testing configuration and
//! provides a helper utility to execute tests.
//! provides a helper utilty to execute tests.
use revive_dt_compiler::{SolidityCompiler, revive_resolc, solc};
use revive_dt_config::TestingPlatform;
use revive_dt_node::{Node, geth, kitchensink::KitchensinkNode};
use revive_dt_node::{geth, kitchensink::KitchensinkNode};
use revive_dt_node_interaction::EthereumNode;
pub mod common;
pub mod driver;
/// One platform can be tested differentially against another.
///
/// For this we need a blockchain node implementation and a compiler.
pub trait Platform {
type Blockchain: EthereumNode + Node;
type Blockchain: EthereumNode;
type Compiler: SolidityCompiler;
/// Returns the matching [TestingPlatform] of the [revive_dt_config::Arguments].
+10 -76
View File
@@ -1,13 +1,5 @@
use std::{
collections::{HashMap, HashSet},
sync::LazyLock,
};
use std::{collections::HashMap, sync::LazyLock};
use alloy::{
network::TxSigner,
primitives::FixedBytes,
signers::{Signature, local::PrivateKeySigner},
};
use clap::Parser;
use rayon::{ThreadPoolBuilder, prelude::*};
@@ -16,11 +8,7 @@ use revive_dt_core::{
Geth, Kitchensink, Platform,
driver::{Driver, State},
};
use revive_dt_format::{
corpus::Corpus,
input::default_caller,
metadata::{AddressReplacementMap, MetadataFile},
};
use revive_dt_format::{corpus::Corpus, metadata::MetadataFile};
use revive_dt_node::pool::NodePool;
use revive_dt_report::reporter::{Report, Span};
use temp_dir::TempDir;
@@ -32,48 +20,12 @@ static TEMP_DIR: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
fn main() -> anyhow::Result<()> {
let args = init_cli()?;
let mut corpora = collect_corpora(&args)?;
let mut replacement_private_keys = HashSet::<FixedBytes<32>>::new();
for case in corpora
.values_mut()
.flat_map(|metadata| metadata.iter_mut())
.flat_map(|metadata| metadata.content.cases.iter_mut())
{
let mut replacement_map = AddressReplacementMap::new();
for address in case.inputs.iter().filter_map(|input| {
if input.caller != default_caller() {
Some(input.caller)
} else {
None
}
}) {
replacement_map.add(address);
}
case.handle_address_replacement(&mut replacement_map)?;
replacement_private_keys.extend(
replacement_map
.into_inner()
.into_values()
.map(|(sk, _)| sk)
.map(|sk| sk.to_bytes()),
);
}
for (corpus, tests) in corpora {
for (corpus, tests) in collect_corpora(&args)? {
let span = Span::new(corpus, args.clone())?;
match &args.compile_only {
Some(platform) => compile_corpus(&args, &tests, platform, span),
None => execute_corpus(
&args,
&tests,
replacement_private_keys
.clone()
.into_iter()
.map(|bytes| PrivateKeySigner::from_bytes(&bytes).expect("Can't fail"))
.collect::<Vec<_>>(),
span,
)?,
None => execute_corpus(&args, &tests, span)?,
}
Report::save()?;
@@ -131,24 +83,15 @@ fn collect_corpora(args: &Arguments) -> anyhow::Result<HashMap<Corpus, Vec<Metad
Ok(corpora)
}
fn run_driver<L, F>(
args: &Arguments,
tests: &[MetadataFile],
additional_signers: impl IntoIterator<Item: TxSigner<Signature> + Send + Sync + 'static>
+ Clone
+ Send
+ Sync
+ 'static,
span: Span,
) -> anyhow::Result<()>
fn run_driver<L, F>(args: &Arguments, tests: &[MetadataFile], span: Span) -> anyhow::Result<()>
where
L: Platform,
F: Platform,
L::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, additional_signers.clone())?;
let follower_nodes = NodePool::<F::Blockchain>::new(args, additional_signers)?;
let leader_nodes = NodePool::<L::Blockchain>::new(args)?;
let follower_nodes = NodePool::<F::Blockchain>::new(args)?;
tests.par_iter().for_each(
|MetadataFile {
@@ -198,22 +141,13 @@ where
Ok(())
}
fn execute_corpus(
args: &Arguments,
tests: &[MetadataFile],
additional_signers: impl IntoIterator<Item: TxSigner<Signature> + Send + Sync + 'static>
+ Clone
+ Send
+ Sync
+ 'static,
span: Span,
) -> anyhow::Result<()> {
fn execute_corpus(args: &Arguments, tests: &[MetadataFile], span: Span) -> anyhow::Result<()> {
match (&args.leader, &args.follower) {
(TestingPlatform::Geth, TestingPlatform::Kitchensink) => {
run_driver::<Geth, Kitchensink>(args, tests, additional_signers, span)?
run_driver::<Geth, Kitchensink>(args, tests, span)?
}
(TestingPlatform::Geth, TestingPlatform::Geth) => {
run_driver::<Geth, Geth>(args, tests, additional_signers, span)?
run_driver::<Geth, Geth>(args, tests, span)?
}
_ => unimplemented!(),
}
+1 -52
View File
@@ -1,11 +1,6 @@
use serde::Deserialize;
use crate::{
define_wrapper_type,
input::{Expected, Input},
metadata::AddressReplacementMap,
mode::Mode,
};
use crate::{input::Input, mode::Mode};
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
pub struct Case {
@@ -14,50 +9,4 @@ pub struct Case {
pub modes: Option<Vec<Mode>>,
pub inputs: Vec<Input>,
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
}
})
}
pub fn handle_address_replacement(
&mut self,
old_to_new_mapping: &mut AddressReplacementMap,
) -> anyhow::Result<()> {
for input in self.inputs.iter_mut() {
input.handle_address_replacement(old_to_new_mapping)?;
}
if let Some(ref mut expected) = self.expected {
expected.handle_address_replacement(old_to_new_mapping)?;
}
Ok(())
}
}
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);
);
+116 -381
View File
@@ -7,29 +7,23 @@ use alloy::{
primitives::{Address, Bytes, U256},
rpc::types::TransactionRequest,
};
use alloy_primitives::{FixedBytes, utils::parse_units};
use semver::VersionReq;
use serde::{Deserialize, Serialize};
use serde::Deserialize;
use serde_json::Value;
use revive_dt_node_interaction::EthereumNode;
use crate::{
define_wrapper_type,
metadata::{AddressReplacementMap, ContractInstance},
};
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
pub struct Input {
#[serde(default = "default_caller")]
pub caller: Address,
pub comment: Option<String>,
#[serde(default = "default_instance")]
pub instance: ContractInstance,
pub instance: String,
pub method: Method,
#[serde(default)]
pub calldata: Calldata,
pub calldata: Option<Calldata>,
pub expected: Option<Expected>,
pub value: Option<EtherValue>,
pub value: Option<String>,
pub storage: Option<HashMap<String, Calldata>>,
}
@@ -43,18 +37,10 @@ pub enum Expected {
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
pub struct ExpectedOutput {
pub compiler_version: Option<VersionReq>,
pub return_data: Option<Calldata>,
pub events: Option<Vec<Event>>,
#[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,
compiler_version: Option<VersionReq>,
return_data: Option<Calldata>,
events: Option<Value>,
exception: Option<bool>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
@@ -85,284 +71,109 @@ pub enum Method {
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
}
pub fn handle_address_replacement(
&mut self,
old_to_new_mapping: &AddressReplacementMap,
) -> anyhow::Result<()> {
if let Some(ref mut calldata) = self.return_data {
calldata.handle_address_replacement(old_to_new_mapping)?;
}
if let Some(ref mut events) = self.events {
for event in events.iter_mut() {
event.handle_address_replacement(old_to_new_mapping)?;
}
}
Ok(())
}
}
impl Event {
pub fn handle_address_replacement(
&mut self,
old_to_new_mapping: &AddressReplacementMap,
) -> anyhow::Result<()> {
if let Some(ref mut address) = self.address {
if let Some(new_address) = old_to_new_mapping.resolve(address.to_string().as_str()) {
*address = new_address
}
};
for topic in self.topics.iter_mut() {
if let Some(new_address) = old_to_new_mapping.resolve(topic.to_string().as_str()) {
*topic = new_address.to_string();
}
}
self.values.handle_address_replacement(old_to_new_mapping)?;
Ok(())
}
}
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 handle_address_replacement(
&mut self,
old_to_new_mapping: &AddressReplacementMap,
) -> anyhow::Result<()> {
match self {
Calldata::Single(_) => {}
Calldata::Compound(items) => {
for item in items.iter_mut() {
if let Some(resolved) = old_to_new_mapping.resolve(item) {
*item = resolved.to_string()
}
}
}
}
Ok(())
}
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 Expected {
pub fn handle_address_replacement(
&mut self,
old_to_new_mapping: &AddressReplacementMap,
) -> anyhow::Result<()> {
match self {
Expected::Calldata(calldata) => {
calldata.handle_address_replacement(old_to_new_mapping)?;
}
Expected::Expected(expected_output) => {
expected_output.handle_address_replacement(old_to_new_mapping)?;
}
Expected::ExpectedMany(expected_outputs) => {
for expected_output in expected_outputs.iter_mut() {
expected_output.handle_address_replacement(old_to_new_mapping)?;
}
}
}
Ok(())
}
}
impl Input {
fn instance_to_address(
&self,
instance: &ContractInstance,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
instance: &str,
deployed_contracts: &HashMap<String, Address>,
) -> anyhow::Result<Address> {
deployed_contracts
.get(instance)
.map(|(a, _)| *a)
.ok_or_else(|| anyhow::anyhow!("instance {instance:?} not deployed"))
.copied()
.ok_or_else(|| anyhow::anyhow!("instance {instance} not deployed"))
}
pub fn encoded_input(
&self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
deployed_abis: &HashMap<String, JsonAbi>,
deployed_contracts: &HashMap<String, Address>,
chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<Bytes> {
match self.method {
Method::Deployer | Method::Fallback => {
let calldata = self
.calldata
.calldata(deployed_contracts, chain_state_provider)?;
let Method::FunctionName(ref function_name) = self.method else {
return Ok(Bytes::default()); // fallback or deployer — no input
};
Ok(calldata.into())
}
Method::FunctionName(ref function_name) => {
let Some(abi) = deployed_contracts.get(&self.instance).map(|(_, a)| a) else {
tracing::error!(
contract_name = self.instance.as_ref(),
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());
};
let Some(abi) = deployed_abis.get(&self.instance) else {
tracing::error!(
contract_name = self.instance,
available_abis = ?deployed_abis.keys().collect::<Vec<_>>(),
"Attempted to lookup ABI of contract but it wasn't found"
);
anyhow::bail!("ABI for instance '{}' not found", &self.instance);
};
tracing::trace!("ABI found for instance: {}", &self.instance.as_ref());
tracing::trace!("ABI found for instance: {}", &self.instance);
// 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
// any existing overloads.
// https://github.com/matter-labs/era-compiler-tester/blob/1dfa7d07cba0734ca97e24704f12dd57f6990c2c/compiler_tester/src/test/case/input/mod.rs#L158-L190
let function = abi
.functions()
.find(|function| function.signature().starts_with(function_name))
.ok_or_else(|| {
anyhow::anyhow!(
"Function with name {:?} not found in ABI for the instance {:?}",
function_name,
&self.instance
)
})?;
// 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
// any existing overloads.
// https://github.com/matter-labs/era-compiler-tester/blob/1dfa7d07cba0734ca97e24704f12dd57f6990c2c/compiler_tester/src/test/case/input/mod.rs#L158-L190
let function = abi
.functions()
.find(|function| function.name.starts_with(function_name))
.ok_or_else(|| {
anyhow::anyhow!(
"Function with name {:?} not found in ABI for the instance {:?}",
function_name,
&self.instance
)
})?;
tracing::trace!("Functions found for instance: {}", self.instance.as_ref());
tracing::trace!("Functions found for instance: {}", &self.instance);
tracing::trace!(
"Starting encoding ABI's parameters for instance: {}",
self.instance.as_ref()
);
let calldata_args = match &self.calldata {
Some(Calldata::Compound(args)) => args,
_ => anyhow::bail!("Expected compound calldata for function call"),
};
// 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 + self.calldata.size_requirement());
calldata.extend(function.selector().0);
self.calldata.calldata_into_slice(
&mut calldata,
deployed_contracts,
chain_state_provider,
)?;
Ok(calldata.into())
}
if calldata_args.len() != function.inputs.len() {
anyhow::bail!(
"Function expects {} args, but got {}",
function.inputs.len(),
calldata_args.len()
);
}
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.
pub fn legacy_transaction(
&self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
nonce: u64,
deployed_contracts: &HashMap<String, Address>,
deployed_abis: &HashMap<String, JsonAbi>,
chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<TransactionRequest> {
let input_data = self.encoded_input(deployed_contracts, chain_state_provider)?;
let transaction_request = TransactionRequest::default().from(self.caller).value(
self.value
.map(|value| value.into_inner())
.unwrap_or_default(),
);
let input_data =
self.encoded_input(deployed_abis, deployed_contracts, chain_state_provider)?;
let transaction_request = TransactionRequest::default().nonce(nonce);
match self.method {
Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)),
_ => Ok(transaction_request
@@ -370,45 +181,14 @@ impl Input {
.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
}
pub fn handle_address_replacement(
&mut self,
old_to_new_mapping: &mut AddressReplacementMap,
) -> anyhow::Result<()> {
if self.caller != default_caller() {
self.caller = old_to_new_mapping.add(self.caller);
}
self.calldata
.handle_address_replacement(old_to_new_mapping)?;
if let Some(ref mut expected) = self.expected {
expected.handle_address_replacement(old_to_new_mapping)?;
}
if let Some(ref mut storage) = self.storage {
for calldata in storage.values_mut() {
calldata.handle_address_replacement(old_to_new_mapping)?;
}
}
Ok(())
}
}
fn default_instance() -> ContractInstance {
ContractInstance::new_from("Test")
fn default_instance() -> String {
"Test".to_string()
}
pub const fn default_caller() -> Address {
Address(FixedBytes(alloy::hex!(
"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"
)))
fn default_caller() -> Address {
"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1".parse().unwrap()
}
/// This function takes in the string calldata argument provided in the JSON input and resolves it
@@ -419,16 +199,15 @@ pub const fn default_caller() -> Address {
/// This piece of code is taken from the matter-labs-tester repository which is licensed under MIT
/// or Apache. The original source code can be found here:
/// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146
pub fn resolve_argument(
fn resolve_argument(
value: &str,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
deployed_contracts: &HashMap<String, Address>,
chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<U256> {
if let Some(instance) = value.strip_suffix(".address") {
Ok(U256::from_be_slice(
deployed_contracts
.get(&ContractInstance::new_from(instance))
.map(|(a, _)| *a)
.get(instance)
.ok_or_else(|| anyhow::anyhow!("Instance `{}` not found", instance))?
.as_ref(),
))
@@ -503,19 +282,22 @@ mod tests {
fn trace_transaction(
&self,
_: &alloy::rpc::types::TransactionReceipt,
_: alloy::rpc::types::trace::geth::GethDebugTracingOptions,
_: alloy::rpc::types::TransactionReceipt,
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
unimplemented!()
}
fn state_diff(
&self,
_: &alloy::rpc::types::TransactionReceipt,
_: alloy::rpc::types::TransactionReceipt,
) -> anyhow::Result<alloy::rpc::types::trace::geth::DiffMode> {
unimplemented!()
}
fn fetch_add_nonce(&self, _: Address) -> anyhow::Result<u64> {
unimplemented!()
}
fn chain_id(&self) -> anyhow::Result<alloy_primitives::ChainId> {
Ok(0x123)
}
@@ -575,19 +357,19 @@ mod tests {
.0;
let input = Input {
instance: ContractInstance::new_from("Contract"),
instance: "Contract".to_string(),
method: Method::FunctionName("store".to_owned()),
calldata: Calldata::Compound(vec!["42".into()]),
calldata: Some(Calldata::Compound(vec!["42".into()])),
..Default::default()
};
let mut contracts = HashMap::new();
contracts.insert(
ContractInstance::new_from("Contract"),
(Address::ZERO, parsed_abi),
);
let mut deployed_abis = HashMap::new();
deployed_abis.insert("Contract".to_string(), parsed_abi);
let deployed_contracts = HashMap::new();
let encoded = input.encoded_input(&contracts, &DummyEthereumNode).unwrap();
let encoded = input
.encoded_input(&deployed_abis, &deployed_contracts, &DummyEthereumNode)
.unwrap();
assert!(encoded.0.starts_with(&selector));
type T = (u64,);
@@ -595,53 +377,6 @@ mod tests {
assert_eq!(decoded.0, 42);
}
#[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: "Contract".to_owned().into(),
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));
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() {
let raw_abi = r#"[
@@ -664,21 +399,21 @@ mod tests {
.0;
let input: Input = Input {
instance: ContractInstance::new_from("Contract"),
instance: "Contract".to_string(),
method: Method::FunctionName("send".to_owned()),
calldata: Calldata::Compound(vec![
calldata: Some(Calldata::Compound(vec![
"0x1000000000000000000000000000000000000001".to_string(),
]),
])),
..Default::default()
};
let mut contracts = HashMap::new();
contracts.insert(
ContractInstance::new_from("Contract"),
(Address::ZERO, parsed_abi),
);
let mut abis = HashMap::new();
abis.insert("Contract".to_string(), parsed_abi);
let contracts = HashMap::new();
let encoded = input.encoded_input(&contracts, &DummyEthereumNode).unwrap();
let encoded = input
.encoded_input(&abis, &contracts, &DummyEthereumNode)
.unwrap();
assert!(encoded.0.starts_with(&selector));
type T = (alloy_primitives::Address,);
-1
View File
@@ -3,6 +3,5 @@
pub mod case;
pub mod corpus;
pub mod input;
pub mod macros;
pub mod metadata;
pub mod mode;
-106
View File
@@ -1,106 +0,0 @@
/// 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
}
}
};
}
+24 -314
View File
@@ -1,21 +1,14 @@
use std::{
collections::{BTreeMap, HashMap},
fmt::Display,
collections::BTreeMap,
fs::{File, read_to_string},
ops::Deref,
path::{Path, PathBuf},
str::FromStr,
};
use alloy::signers::local::PrivateKeySigner;
use alloy_primitives::Address;
use revive_dt_node_interaction::EthereumNode;
use serde::{Deserialize, Serialize};
use serde::Deserialize;
use crate::{
case::Case,
define_wrapper_type,
input::resolve_argument,
mode::{Mode, SolcMode},
};
@@ -48,10 +41,8 @@ impl Deref for MetadataFile {
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
pub struct Metadata {
pub targets: Option<Vec<String>>,
pub cases: Vec<Case>,
pub contracts: Option<BTreeMap<ContractInstance, ContractPathAndIdentifier>>,
// TODO: Convert into wrapper types for clarity.
pub contracts: Option<BTreeMap<String, String>>,
pub libraries: Option<BTreeMap<String, BTreeMap<String, String>>>,
pub ignore: Option<bool>,
pub modes: Option<Vec<Mode>>,
@@ -86,35 +77,28 @@ impl Metadata {
.to_path_buf())
}
/// Returns the contract sources with canonicalized paths for the files
pub fn contract_sources(
&self,
) -> anyhow::Result<BTreeMap<ContractInstance, ContractPathAndIdentifier>> {
/// Extract the contract sources.
///
/// Returns a mapping of contract IDs to their source path and contract name.
pub fn contract_sources(&self) -> anyhow::Result<BTreeMap<String, (PathBuf, String)>> {
let directory = self.directory()?;
let mut sources = BTreeMap::new();
let Some(contracts) = &self.contracts else {
return Ok(sources);
};
for (
alias,
ContractPathAndIdentifier {
contract_source_path,
contract_ident,
},
) in contracts
{
let alias = alias.clone();
let absolute_path = directory.join(contract_source_path).canonicalize()?;
let contract_ident = contract_ident.clone();
for (id, contract) in contracts {
// TODO: broken if a colon is in the dir name..
let mut parts = contract.split(':');
let (Some(file_name), Some(contract_name)) = (parts.next(), parts.next()) else {
anyhow::bail!("metadata contains invalid contract: {contract}");
};
let file = directory.to_path_buf().join(file_name);
if !file.is_file() {
anyhow::bail!("contract {id} is not a file: {}", file.display());
}
sources.insert(
alias,
ContractPathAndIdentifier {
contract_source_path: absolute_path,
contract_ident,
},
);
sources.insert(id.clone(), (file, contract_name.to_string()));
}
Ok(sources)
@@ -194,16 +178,12 @@ impl Metadata {
match serde_json::from_str::<Self>(&spec) {
Ok(mut metadata) => {
metadata.file_path = Some(path.to_path_buf());
metadata.contracts = Some(
[(
ContractInstance::new_from("test"),
ContractPathAndIdentifier {
contract_source_path: path.to_path_buf(),
contract_ident: ContractIdent::new_from("Test"),
},
)]
.into(),
);
let name = path
.file_name()
.expect("this should be the path to a Solidity file")
.to_str()
.expect("the file name should be valid UTF-8k");
metadata.contracts = Some([(String::from("Test"), format!("{name}:Test"))].into());
Some(metadata)
}
Err(error) => {
@@ -215,274 +195,4 @@ impl Metadata {
}
}
}
pub fn handle_address_replacement(
&mut self,
old_to_new_mapping: &mut AddressReplacementMap,
) -> anyhow::Result<()> {
for case in self.cases.iter_mut() {
case.handle_address_replacement(old_to_new_mapping)?;
}
tracing::debug!(metadata = ?self, "Performed replacement on metadata");
Ok(())
}
}
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()
}
}
#[derive(Clone, Debug, Default)]
pub struct AddressReplacementMap(HashMap<Address, (PrivateKeySigner, Address)>);
impl AddressReplacementMap {
pub fn new() -> Self {
Self(Default::default())
}
pub fn into_inner(self) -> HashMap<Address, (PrivateKeySigner, Address)> {
self.0
}
pub fn contains_key(&self, address: &Address) -> bool {
self.0.contains_key(address)
}
pub fn add(&mut self, address: Address) -> Address {
self.0
.entry(address)
.or_insert_with(|| {
let private_key = Self::new_random_private_key_signer();
let account = private_key.address();
tracing::debug!(
old_address = %address,
new_address = %account,
"Added a new address replacement"
);
(private_key, account)
})
.1
}
pub fn resolve(&self, value: &str) -> Option<Address> {
// We attempt to resolve the given string without any additional context of the deployed
// contracts or the node API as we do not need them. If the resolution fails then we know
// that this isn't an address and we skip it.
let Ok(resolved) = resolve_argument(value, &Default::default(), &UnimplementedEthereumNode)
else {
return None;
};
let resolved_bytes = resolved.to_be_bytes_trimmed_vec();
let Ok(address) = Address::try_from(resolved_bytes.as_slice()) else {
return None;
};
self.0.get(&address).map(|(_, address)| *address)
}
fn new_random_private_key_signer() -> PrivateKeySigner {
// TODO: Use a seedable RNG to allow for deterministic allocation of the private keys so
// that we get reproducible runs.
PrivateKeySigner::random()
}
}
impl AsRef<HashMap<Address, (PrivateKeySigner, Address)>> for AddressReplacementMap {
fn as_ref(&self) -> &HashMap<Address, (PrivateKeySigner, Address)> {
&self.0
}
}
struct UnimplementedEthereumNode;
impl EthereumNode for UnimplementedEthereumNode {
fn execute_transaction(
&self,
_: alloy::rpc::types::TransactionRequest,
) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> {
anyhow::bail!("Unimplemented")
}
fn chain_id(&self) -> anyhow::Result<alloy_primitives::ChainId> {
anyhow::bail!("Unimplemented")
}
fn block_gas_limit(&self, _: alloy::eips::BlockNumberOrTag) -> anyhow::Result<u128> {
anyhow::bail!("Unimplemented")
}
fn block_coinbase(&self, _: alloy::eips::BlockNumberOrTag) -> anyhow::Result<Address> {
anyhow::bail!("Unimplemented")
}
fn block_difficulty(
&self,
_: alloy::eips::BlockNumberOrTag,
) -> anyhow::Result<alloy_primitives::U256> {
anyhow::bail!("Unimplemented")
}
fn block_hash(
&self,
_: alloy::eips::BlockNumberOrTag,
) -> anyhow::Result<alloy_primitives::BlockHash> {
anyhow::bail!("Unimplemented")
}
fn block_timestamp(
&self,
_: alloy::eips::BlockNumberOrTag,
) -> anyhow::Result<alloy_primitives::BlockTimestamp> {
anyhow::bail!("Unimplemented")
}
fn last_block_number(&self) -> anyhow::Result<alloy_primitives::BlockNumber> {
anyhow::bail!("Unimplemented")
}
fn trace_transaction(
&self,
_: &alloy::rpc::types::TransactionReceipt,
_: alloy::rpc::types::trace::geth::GethDebugTracingOptions,
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
anyhow::bail!("Unimplemented")
}
fn state_diff(
&self,
_: &alloy::rpc::types::TransactionReceipt,
) -> anyhow::Result<alloy::rpc::types::trace::geth::DiffMode> {
anyhow::bail!("Unimplemented")
}
}
#[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");
}
}
@@ -9,7 +9,6 @@ use tokio::{
runtime::Builder,
sync::{mpsc::UnboundedSender, oneshot},
};
use tracing::Instrument;
/// A blocking async executor.
///
@@ -64,11 +63,6 @@ impl BlockingExecutor {
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<TaskMessage>();
thread::spawn(move || {
tracing::info!(
thread_id = ?std::thread::current().id(),
"Starting async runtime thread"
);
let runtime = Builder::new_current_thread()
.enable_all()
.build()
@@ -113,9 +107,7 @@ impl BlockingExecutor {
// 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
// convert it back into R.
let future = Box::pin(
async move { Box::new(future.await) as Box<dyn Any + Send> }.in_current_span(),
);
let future = Box::pin(async move { Box::new(future.await) as Box<dyn Any + Send> });
let task = TaskMessage::new(future, response_tx);
if let Err(error) = STATE.tx.send(task) {
+6 -7
View File
@@ -2,7 +2,7 @@
use alloy::eips::BlockNumberOrTag;
use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, U256};
use alloy::rpc::types::trace::geth::{DiffMode, GethDebugTracingOptions, GethTrace};
use alloy::rpc::types::trace::geth::{DiffMode, GethTrace};
use alloy::rpc::types::{TransactionReceipt, TransactionRequest};
use anyhow::Result;
@@ -15,14 +15,13 @@ pub trait EthereumNode {
fn execute_transaction(&self, transaction: TransactionRequest) -> Result<TransactionReceipt>;
/// Trace the transaction in the [TransactionReceipt] and return a [GethTrace].
fn trace_transaction(
&self,
receipt: &TransactionReceipt,
trace_options: GethDebugTracingOptions,
) -> Result<GethTrace>;
fn trace_transaction(&self, transaction: TransactionReceipt) -> Result<GethTrace>;
/// Returns the state diff of the transaction hash in the [TransactionReceipt].
fn state_diff(&self, receipt: &TransactionReceipt) -> Result<DiffMode>;
fn state_diff(&self, transaction: 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.
fn chain_id(&self) -> Result<ChainId>;
-78
View File
@@ -1,78 +0,0 @@
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)
}
}
}
+53 -71
View File
@@ -1,35 +1,37 @@
//! The go-ethereum node implementation.
use std::{
collections::HashMap,
fs::{File, OpenOptions, create_dir_all, remove_dir_all},
io::{BufRead, BufReader, Read, Write},
path::PathBuf,
process::{Child, Command, Stdio},
sync::atomic::{AtomicU32, Ordering},
sync::{
Mutex,
atomic::{AtomicU32, Ordering},
},
time::{Duration, Instant},
};
use alloy::{
eips::BlockNumberOrTag,
genesis::{Genesis, GenesisAccount},
network::{Ethereum, EthereumWallet, NetworkWallet, TxSigner},
network::{Ethereum, EthereumWallet},
primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, U256},
providers::{
Provider, ProviderBuilder,
ext::DebugApi,
fillers::{CachedNonceManager, ChainIdFiller, FillProvider, NonceFiller, TxFiller},
fillers::{FillProvider, TxFiller},
},
rpc::types::{
TransactionReceipt, TransactionRequest,
trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame},
},
signers::Signature,
};
use revive_dt_config::Arguments;
use revive_dt_node_interaction::{BlockingExecutor, EthereumNode};
use tracing::Level;
use crate::{Node, common::FallbackGasFiller};
use crate::Node;
static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
@@ -52,7 +54,7 @@ pub struct Instance {
network_id: u64,
start_timeout: u64,
wallet: EthereumWallet,
nonce_manager: CachedNonceManager,
nonces: Mutex<HashMap<Address, u64>>,
/// 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
/// separate fields) as the logic that we need to apply to them is all the same regardless of
@@ -80,17 +82,8 @@ impl Instance {
create_dir_all(&self.base_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)
{
genesis.alloc.entry(signer_address).or_insert(
GenesisAccount::default()
.with_balance(10000000000000000000000u128.try_into().unwrap()),
);
}
let genesis_path = self.base_directory.join(Self::GENESIS_JSON_FILE);
serde_json::to_writer(File::create(&genesis_path)?, &genesis)?;
File::create(&genesis_path)?.write_all(genesis.as_bytes())?;
let mut child = Command::new(&self.geth)
.arg("init")
@@ -213,19 +206,8 @@ impl Instance {
> + 'static {
let connection_string = self.connection_string();
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 {
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)
.connect(&connection_string)
.await
@@ -242,7 +224,7 @@ impl EthereumNode for Instance {
) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> {
let provider = self.provider();
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 provider = provider.await?;
@@ -323,28 +305,30 @@ impl EthereumNode for Instance {
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
fn trace_transaction(
&self,
transaction: &TransactionReceipt,
trace_options: GethDebugTracingOptions,
transaction: TransactionReceipt,
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
let tx_hash = transaction.transaction_hash;
let provider = self.provider();
BlockingExecutor::execute(async move {
Ok(provider
.await?
.debug_trace_transaction(tx_hash, trace_options)
.await?)
})?
}
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
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,
});
let provider = self.provider();
BlockingExecutor::execute(async move {
Ok(provider
.await?
.debug_trace_transaction(transaction.transaction_hash, trace_options)
.await?)
})?
}
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
fn state_diff(
&self,
transaction: alloy::rpc::types::TransactionReceipt,
) -> anyhow::Result<DiffMode> {
match self
.trace_transaction(transaction, trace_options)?
.trace_transaction(transaction)?
.try_into_pre_state_frame()?
{
PreStateFrame::Diff(diff) => Ok(diff),
@@ -352,6 +336,24 @@ 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))]
fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
let provider = self.provider();
@@ -435,19 +437,11 @@ impl EthereumNode for Instance {
}
impl Node for Instance {
fn new(
config: &Arguments,
additional_signers: impl IntoIterator<Item: TxSigner<Signature> + Send + Sync + 'static>,
) -> Self {
fn new(config: &Arguments) -> Self {
let geth_directory = config.directory().join(Self::BASE_DIRECTORY);
let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst);
let base_directory = geth_directory.join(id.to_string());
let mut wallet = config.wallet();
for signer in additional_signers {
wallet.register_signer(signer);
}
Self {
connection_string: base_directory.join(Self::IPC_FILE).display().to_string(),
data_directory: base_directory.join(Self::DATA_DIRECTORY),
@@ -458,11 +452,11 @@ impl Node for Instance {
handle: None,
network_id: config.network_id,
start_timeout: config.geth_start_timeout,
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
// the vector. It's the stdout and stderr of the geth node.
logs_file_to_flush: Vec::with_capacity(2),
nonce_manager: Default::default(),
}
}
@@ -511,14 +505,6 @@ impl Node for Instance {
.stdout;
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 {
@@ -531,8 +517,6 @@ impl Drop for Instance {
#[cfg(test)]
mod tests {
use revive_dt_config::Arguments;
use alloy::signers::local::PrivateKeySigner;
use temp_dir::TempDir;
use crate::{GENESIS_JSON, Node};
@@ -549,7 +533,7 @@ mod tests {
fn new_node() -> (Instance, TempDir) {
let (args, temp_dir) = test_config();
let mut node = Instance::new(&args, Vec::<PrivateKeySigner>::with_capacity(0));
let mut node = Instance::new(&args);
node.init(GENESIS_JSON.to_owned())
.expect("Failed to initialize the node")
.spawn_process()
@@ -559,23 +543,21 @@ mod tests {
#[test]
fn init_works() {
Instance::new(&test_config().0, Vec::<PrivateKeySigner>::with_capacity(0))
Instance::new(&test_config().0)
.init(GENESIS_JSON.to_string())
.unwrap();
}
#[test]
fn spawn_works() {
Instance::new(&test_config().0, Vec::<PrivateKeySigner>::with_capacity(0))
Instance::new(&test_config().0)
.spawn(GENESIS_JSON.to_string())
.unwrap();
}
#[test]
fn version_works() {
let version = Instance::new(&test_config().0, Vec::<PrivateKeySigner>::with_capacity(0))
.version()
.unwrap();
let version = Instance::new(&test_config().0).version().unwrap();
assert!(
version.starts_with("geth version"),
"expected version string, got: '{version}'"
+98 -110
View File
@@ -1,32 +1,35 @@
use std::{
collections::HashMap,
fs::{File, OpenOptions, create_dir_all, remove_dir_all},
io::{BufRead, Write},
path::{Path, PathBuf},
process::{Child, Command, Stdio},
sync::atomic::{AtomicU32, Ordering},
sync::{
Mutex,
atomic::{AtomicU32, Ordering},
},
time::Duration,
};
use alloy::{
consensus::{BlockHeader, TxEnvelope},
eips::BlockNumberOrTag,
genesis::{Genesis, GenesisAccount},
hex,
network::{
Ethereum, EthereumWallet, Network, NetworkWallet, TransactionBuilder,
TransactionBuilderError, TxSigner, UnbuiltTransactionError,
Ethereum, EthereumWallet, Network, TransactionBuilder, TransactionBuilderError,
UnbuiltTransactionError,
},
primitives::{Address, B64, B256, BlockHash, BlockNumber, BlockTimestamp, Bloom, Bytes, U256},
providers::{
Provider, ProviderBuilder,
ext::DebugApi,
fillers::{CachedNonceManager, ChainIdFiller, FillProvider, NonceFiller, TxFiller},
fillers::{FillProvider, TxFiller},
},
rpc::types::{
TransactionReceipt,
eth::{Block, Header, Transaction},
trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame},
},
signers::Signature,
};
use serde::{Deserialize, Serialize};
use serde_json::{Value as JsonValue, json};
@@ -37,7 +40,7 @@ use tracing::Level;
use revive_dt_config::Arguments;
use revive_dt_node_interaction::{BlockingExecutor, EthereumNode};
use crate::{Node, common::FallbackGasFiller};
use crate::Node;
static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
@@ -52,7 +55,7 @@ pub struct KitchensinkNode {
logs_directory: PathBuf,
process_substrate: Option<Child>,
process_proxy: Option<Child>,
nonce_manager: CachedNonceManager,
nonces: Mutex<HashMap<Address, u64>>,
/// 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
/// separate fields) as the logic that we need to apply to them is all the same regardless of
@@ -124,18 +127,7 @@ impl KitchensinkNode {
None
})
.collect();
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)
{
genesis.alloc.entry(signer_address).or_insert(
GenesisAccount::default()
.with_balance(10000000000000000000000u128.try_into().unwrap()),
);
}
self.extract_balance_from_genesis_file(&genesis)?
};
let mut eth_balances = self.extract_balance_from_genesis_file(genesis)?;
merged_balances.append(&mut eth_balances);
chainspec_json["genesis"]["runtimeGenesis"]["patch"]["balances"]["balances"] =
@@ -249,27 +241,42 @@ impl KitchensinkNode {
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
fn extract_balance_from_genesis_file(
&self,
genesis: &Genesis,
genesis_str: &str,
) -> anyhow::Result<Vec<(String, u128)>> {
genesis
.alloc
.iter()
.try_fold(Vec::new(), |mut vec, (address, acc)| {
let substrate_address = Self::eth_to_substrate_address(address);
let balance = acc.balance.try_into()?;
vec.push((substrate_address, balance));
Ok(vec)
})
let genesis_json: JsonValue = serde_json::from_str(genesis_str)?;
let alloc = genesis_json
.get("alloc")
.and_then(|a| a.as_object())
.ok_or_else(|| anyhow::anyhow!("Missing 'alloc' in genesis"))?;
let mut balances = Vec::new();
for (eth_addr, obj) in alloc.iter() {
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(address: &Address) -> String {
let eth_bytes = address.0.0;
fn eth_to_substrate_address(eth_addr: &str) -> anyhow::Result<String> {
let eth_bytes = hex::decode(eth_addr.trim_start_matches("0x"))?;
if eth_bytes.len() != 20 {
anyhow::bail!(
"Invalid Ethereum address length: expected 20 bytes, got {}",
eth_bytes.len()
);
}
let mut padded = [0xEEu8; 32];
padded[..20].copy_from_slice(&eth_bytes);
let account_id = AccountId32::from(padded);
account_id.to_ss58check()
Ok(account_id.to_ss58check())
}
fn wait_ready(logs_file_path: &Path, marker: &str, timeout: Duration) -> anyhow::Result<()> {
@@ -343,24 +350,9 @@ impl KitchensinkNode {
> + 'static {
let connection_string = self.connection_string();
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 {
ProviderBuilder::new()
.disable_recommended_fillers()
.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)
.connect(&connection_string)
.await
@@ -392,28 +384,27 @@ impl EthereumNode for KitchensinkNode {
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
fn trace_transaction(
&self,
transaction: &TransactionReceipt,
trace_options: GethDebugTracingOptions,
transaction: TransactionReceipt,
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
let tx_hash = transaction.transaction_hash;
let provider = self.provider();
BlockingExecutor::execute(async move {
Ok(provider
.await?
.debug_trace_transaction(tx_hash, trace_options)
.await?)
})?
}
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
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,
});
let provider = self.provider();
BlockingExecutor::execute(async move {
Ok(provider
.await?
.debug_trace_transaction(transaction.transaction_hash, trace_options)
.await?)
})?
}
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
fn state_diff(&self, transaction: TransactionReceipt) -> anyhow::Result<DiffMode> {
match self
.trace_transaction(transaction, trace_options)?
.trace_transaction(transaction)?
.try_into_pre_state_frame()?
{
PreStateFrame::Diff(diff) => Ok(diff),
@@ -421,6 +412,24 @@ 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))]
fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
let provider = self.provider();
@@ -504,31 +513,23 @@ impl EthereumNode for KitchensinkNode {
}
impl Node for KitchensinkNode {
fn new(
config: &Arguments,
additional_signers: impl IntoIterator<Item: TxSigner<Signature> + Send + Sync + 'static>,
) -> Self {
fn new(config: &Arguments) -> Self {
let kitchensink_directory = config.directory().join(Self::BASE_DIRECTORY);
let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst);
let base_directory = kitchensink_directory.join(id.to_string());
let logs_directory = base_directory.join(Self::LOGS_DIRECTORY);
let mut wallet = config.wallet();
for signer in additional_signers {
wallet.register_signer(signer);
}
Self {
id,
substrate_binary: config.kitchensink.clone(),
eth_proxy_binary: config.eth_proxy.clone(),
rpc_url: String::new(),
wallet,
wallet: config.wallet(),
base_directory,
logs_directory,
process_substrate: None,
process_proxy: None,
nonce_manager: Default::default(),
nonces: Mutex::new(HashMap::new()),
// 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.
logs_file_to_flush: Vec::with_capacity(4),
@@ -584,14 +585,6 @@ impl Node for KitchensinkNode {
.stdout;
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 {
@@ -647,12 +640,6 @@ 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> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::input(self)
}
@@ -1030,10 +1017,10 @@ impl BlockHeader for KitchenSinkHeader {
#[cfg(test)]
mod tests {
use alloy::{rpc::types::TransactionRequest, signers::local::PrivateKeySigner};
use alloy::rpc::types::TransactionRequest;
use revive_dt_config::Arguments;
use std::path::PathBuf;
use std::sync::{LazyLock, Mutex};
use std::sync::LazyLock;
use temp_dir::TempDir;
use std::fs;
@@ -1073,7 +1060,7 @@ mod tests {
let _guard = NODE_START_MUTEX.lock().unwrap();
let (args, temp_dir) = test_config();
let mut node = KitchensinkNode::new(&args, Vec::<PrivateKeySigner>::with_capacity(0));
let mut node = KitchensinkNode::new(&args);
node.init(GENESIS_JSON)
.expect("Failed to initialize the node")
.spawn_process()
@@ -1128,8 +1115,7 @@ mod tests {
}
"#;
let mut dummy_node =
KitchensinkNode::new(&test_config().0, Vec::<PrivateKeySigner>::with_capacity(0));
let mut dummy_node = KitchensinkNode::new(&test_config().0);
// Call `init()`
dummy_node.init(genesis_content).expect("init failed");
@@ -1143,12 +1129,12 @@ mod tests {
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
let first_eth_addr = KitchensinkNode::eth_to_substrate_address(
&"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1".parse().unwrap(),
);
let second_eth_addr = KitchensinkNode::eth_to_substrate_address(
&"Ab8483F64d9C6d1EcF9b849Ae677dD3315835cb2".parse().unwrap(),
);
let first_eth_addr =
KitchensinkNode::eth_to_substrate_address("90F8bf6A479f320ead074411a4B0e7944Ea8c9C1")
.unwrap();
let second_eth_addr =
KitchensinkNode::eth_to_substrate_address("Ab8483F64d9C6d1EcF9b849Ae677dD3315835cb2")
.unwrap();
assert!(
contents.contains(&first_eth_addr),
@@ -1173,11 +1159,10 @@ mod tests {
}
"#;
let node =
KitchensinkNode::new(&test_config().0, Vec::<PrivateKeySigner>::with_capacity(0));
let node = KitchensinkNode::new(&test_config().0);
let result = node
.extract_balance_from_genesis_file(&serde_json::from_str(genesis_json).unwrap())
.extract_balance_from_genesis_file(genesis_json)
.unwrap();
let result_map: std::collections::HashMap<_, _> = result.into_iter().collect();
@@ -1207,7 +1192,7 @@ mod tests {
];
for eth_addr in eth_addresses {
let ss58 = KitchensinkNode::eth_to_substrate_address(&eth_addr.parse().unwrap());
let ss58 = KitchensinkNode::eth_to_substrate_address(eth_addr).unwrap();
println!("Ethereum: {eth_addr} -> Substrate SS58: {ss58}");
}
@@ -1235,7 +1220,7 @@ mod tests {
];
for (eth_addr, expected_ss58) in cases {
let result = KitchensinkNode::eth_to_substrate_address(&eth_addr.parse().unwrap());
let result = KitchensinkNode::eth_to_substrate_address(eth_addr).unwrap();
assert_eq!(
result, expected_ss58,
"Mismatch for Ethereum address {eth_addr}"
@@ -1247,7 +1232,7 @@ mod tests {
fn spawn_works() {
let (config, _temp_dir) = test_config();
let mut node = KitchensinkNode::new(&config, Vec::<PrivateKeySigner>::with_capacity(0));
let mut node = KitchensinkNode::new(&config);
node.spawn(GENESIS_JSON.to_string()).unwrap();
}
@@ -1255,7 +1240,7 @@ mod tests {
fn version_works() {
let (config, _temp_dir) = test_config();
let node = KitchensinkNode::new(&config, Vec::<PrivateKeySigner>::with_capacity(0));
let node = KitchensinkNode::new(&config);
let version = node.version().unwrap();
assert!(
@@ -1268,7 +1253,7 @@ mod tests {
fn eth_rpc_version_works() {
let (config, _temp_dir) = test_config();
let node = KitchensinkNode::new(&config, Vec::<PrivateKeySigner>::with_capacity(0));
let node = KitchensinkNode::new(&config);
let version = node.eth_rpc_version().unwrap();
assert!(
@@ -1311,7 +1296,8 @@ mod tests {
let coinbase = node.block_coinbase(BlockNumberOrTag::Latest);
// Assert
let _ = coinbase.expect("Failed to get the coinbase");
let coinbase = coinbase.expect("Failed to get the coinbase");
assert_eq!(coinbase, Address::ZERO)
}
#[test]
@@ -1323,7 +1309,8 @@ mod tests {
let block_difficulty = node.block_difficulty(BlockNumberOrTag::Latest);
// Assert
let _ = block_difficulty.expect("Failed to get the block difficulty");
let block_difficulty = block_difficulty.expect("Failed to get the block difficulty");
assert_eq!(block_difficulty, U256::ZERO)
}
#[test]
@@ -1359,6 +1346,7 @@ mod tests {
let block_number = node.last_block_number();
// Assert
let _ = block_number.expect("Failed to get the block number");
let block_number = block_number.expect("Failed to get the block number");
assert_eq!(block_number, 0)
}
}
+1 -10
View File
@@ -1,10 +1,8 @@
//! This crate implements the testing nodes.
use alloy::{network::TxSigner, signers::Signature};
use revive_dt_config::Arguments;
use revive_dt_node_interaction::EthereumNode;
pub mod common;
pub mod geth;
pub mod kitchensink;
pub mod pool;
@@ -15,10 +13,7 @@ pub const GENESIS_JSON: &str = include_str!("../../../genesis.json");
/// An abstract interface for testing nodes.
pub trait Node: EthereumNode {
/// Create a new uninitialized instance.
fn new(
config: &Arguments,
additional_signers: impl IntoIterator<Item: TxSigner<Signature> + Send + Sync + 'static>,
) -> Self;
fn new(config: &Arguments) -> Self;
/// Spawns a node configured according to the genesis json.
///
@@ -35,8 +30,4 @@ pub trait Node: EthereumNode {
/// Returns the node version.
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;
}
+4 -19
View File
@@ -6,7 +6,6 @@ use std::{
thread,
};
use alloy::{network::TxSigner, signers::Signature};
use anyhow::Context;
use revive_dt_config::Arguments;
@@ -24,14 +23,7 @@ where
T: Node + Send + 'static,
{
/// Create a new Pool. This will start as many nodes as there are workers in `config`.
pub fn new(
config: &Arguments,
additional_signers: impl IntoIterator<Item: TxSigner<Signature> + Send + Sync + 'static>
+ Clone
+ Send
+ Sync
+ 'static,
) -> anyhow::Result<Self> {
pub fn new(config: &Arguments) -> anyhow::Result<Self> {
let nodes = config.workers;
let genesis = read_to_string(&config.genesis_file).context(format!(
"can not read genesis file: {}",
@@ -42,10 +34,7 @@ where
for _ in 0..nodes {
let config = config.clone();
let genesis = genesis.clone();
let additional_signers = additional_signers.clone();
handles.push(thread::spawn(move || {
spawn_node::<T>(&config, additional_signers, genesis)
}));
handles.push(thread::spawn(move || spawn_node::<T>(&config, genesis)));
}
let mut nodes = Vec::with_capacity(nodes);
@@ -71,12 +60,8 @@ where
}
}
fn spawn_node<T: Node + Send>(
args: &Arguments,
additional_signers: impl IntoIterator<Item: TxSigner<Signature> + Send + Sync + 'static>,
genesis: String,
) -> anyhow::Result<T> {
let mut node = T::new(args, additional_signers);
fn spawn_node<T: Node + Send>(args: &Arguments, genesis: String) -> anyhow::Result<T> {
let mut node = T::new(args);
tracing::info!("starting node: {}", node.connection_string());
node.spawn(genesis)?;
Ok(node)
+16 -4
View File
@@ -110,25 +110,37 @@ mod tests {
#[test]
fn try_get_windows() {
let version = List::download(List::WINDOWS_URL).unwrap().latest_release;
let version = List::download(List::WINDOWS_URL)
.unwrap()
.latest_release
.into();
GHDownloader::windows(version).download().unwrap();
}
#[test]
fn try_get_macosx() {
let version = List::download(List::MACOSX_URL).unwrap().latest_release;
let version = List::download(List::MACOSX_URL)
.unwrap()
.latest_release
.into();
GHDownloader::macosx(version).download().unwrap();
}
#[test]
fn try_get_linux() {
let version = List::download(List::LINUX_URL).unwrap().latest_release;
let version = List::download(List::LINUX_URL)
.unwrap()
.latest_release
.into();
GHDownloader::linux(version).download().unwrap();
}
#[test]
fn try_get_wasm() {
let version = List::download(List::WASM_URL).unwrap().latest_release;
let version = List::download(List::WASM_URL)
.unwrap()
.latest_release
.into();
GHDownloader::wasm(version).download().unwrap();
}
}
+1 -1
View File
@@ -35,7 +35,7 @@
"timestamp": "0x00",
"alloc": {
"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1": {
"balance": "10000000000000000000000"
"balance": "1000000000000000000"
}
}
}