mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-04-23 06:07:58 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 93f6e5efdf | |||
| ecd6252666 | |||
| 1d1642887b | |||
| db0522aa71 | |||
| ca5cad8e43 | |||
| 33911b5ce3 | |||
| 5b730d914e | |||
| 9f7a314b20 | |||
| 5cd3dd8c83 | |||
| 8d15f87ff0 | |||
| 566dd06d9a | |||
| 5c30e8a5bf | |||
| c2ba2cfed6 | |||
| 3dda739cef | |||
| 97e3f8bbff | |||
| 7189361a58 | |||
| 9b700bfec2 | |||
| 98b62d705f |
@@ -91,10 +91,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
rustflags: ""
|
rustflags: ""
|
||||||
|
|
||||||
- name: Add wasm32 target
|
- name: Add wasm32 target and formatting
|
||||||
run: |
|
run: |
|
||||||
rustup target add wasm32-unknown-unknown
|
rustup target add wasm32-unknown-unknown
|
||||||
rustup component add rust-src
|
rustup component add rust-src rustfmt clippy
|
||||||
|
|
||||||
- name: Install Geth on Ubuntu
|
- name: Install Geth on Ubuntu
|
||||||
if: matrix.os == 'ubuntu-24.04'
|
if: matrix.os == 'ubuntu-24.04'
|
||||||
@@ -141,6 +141,17 @@ jobs:
|
|||||||
chmod +x resolc
|
chmod +x resolc
|
||||||
sudo mv resolc /usr/local/bin
|
sudo mv resolc /usr/local/bin
|
||||||
|
|
||||||
|
- name: Install Kurtosis on macOS
|
||||||
|
if: matrix.os == 'macos-14'
|
||||||
|
run: brew install kurtosis-tech/tap/kurtosis-cli
|
||||||
|
|
||||||
|
- name: Install Kurtosis on Ubuntu
|
||||||
|
if: matrix.os == 'ubuntu-24.04'
|
||||||
|
run: |
|
||||||
|
echo "deb [trusted=yes] https://apt.fury.io/kurtosis-tech/ /" | sudo tee /etc/apt/sources.list.d/kurtosis.list
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install kurtosis-cli
|
||||||
|
|
||||||
- name: Machete
|
- name: Machete
|
||||||
uses: bnjbvr/cargo-machete@v0.7.1
|
uses: bnjbvr/cargo-machete@v0.7.1
|
||||||
|
|
||||||
@@ -159,5 +170,22 @@ jobs:
|
|||||||
- name: Check resolc version
|
- name: Check resolc version
|
||||||
run: resolc --version
|
run: resolc --version
|
||||||
|
|
||||||
- name: Test cargo workspace
|
- name: Test Formatting
|
||||||
run: make test
|
run: make format
|
||||||
|
|
||||||
|
- name: Test Clippy
|
||||||
|
run: make clippy
|
||||||
|
|
||||||
|
- name: Test Machete
|
||||||
|
run: make machete
|
||||||
|
|
||||||
|
- name: Unit Tests
|
||||||
|
if: matrix.os == 'ubuntu-24.04'
|
||||||
|
run: cargo test --workspace -- --nocapture
|
||||||
|
|
||||||
|
# We can't install docker in the MacOS image used in CI and therefore we need to skip the
|
||||||
|
# Kurtosis and lighthouse related tests when running the CI on MacOS.
|
||||||
|
- name: Unit Tests
|
||||||
|
if: matrix.os == 'macos-14'
|
||||||
|
run: |
|
||||||
|
cargo test --workspace -- --nocapture --skip lighthouse_geth::tests::
|
||||||
|
|||||||
@@ -13,3 +13,4 @@ resolc-compiler-tests
|
|||||||
workdir
|
workdir
|
||||||
|
|
||||||
!/schema.json
|
!/schema.json
|
||||||
|
!/dev-genesis.json
|
||||||
Generated
+162
@@ -87,6 +87,7 @@ dependencies = [
|
|||||||
"alloy-transport",
|
"alloy-transport",
|
||||||
"alloy-transport-http",
|
"alloy-transport-http",
|
||||||
"alloy-transport-ipc",
|
"alloy-transport-ipc",
|
||||||
|
"alloy-transport-ws",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -378,6 +379,7 @@ dependencies = [
|
|||||||
"alloy-transport",
|
"alloy-transport",
|
||||||
"alloy-transport-http",
|
"alloy-transport-http",
|
||||||
"alloy-transport-ipc",
|
"alloy-transport-ipc",
|
||||||
|
"alloy-transport-ws",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"auto_impl",
|
"auto_impl",
|
||||||
@@ -454,6 +456,7 @@ dependencies = [
|
|||||||
"alloy-transport",
|
"alloy-transport",
|
||||||
"alloy-transport-http",
|
"alloy-transport-http",
|
||||||
"alloy-transport-ipc",
|
"alloy-transport-ipc",
|
||||||
|
"alloy-transport-ws",
|
||||||
"futures",
|
"futures",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -709,6 +712,24 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alloy-transport-ws"
|
||||||
|
version = "1.0.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e915e1250dc129ad48d264573ccd08e4716fdda564a772fd217875b8459aff9"
|
||||||
|
dependencies = [
|
||||||
|
"alloy-pubsub",
|
||||||
|
"alloy-transport",
|
||||||
|
"futures",
|
||||||
|
"http",
|
||||||
|
"rustls",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"tokio-tungstenite",
|
||||||
|
"tracing",
|
||||||
|
"ws_stream_wasm",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-trie"
|
name = "alloy-trie"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -1373,6 +1394,17 @@ dependencies = [
|
|||||||
"syn 2.0.101",
|
"syn 2.0.101",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async_io_stream"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c"
|
||||||
|
dependencies = [
|
||||||
|
"futures",
|
||||||
|
"pharos",
|
||||||
|
"rustc_version 0.4.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atomic-waker"
|
name = "atomic-waker"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
@@ -2021,6 +2053,12 @@ dependencies = [
|
|||||||
"parking_lot_core",
|
"parking_lot_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "data-encoding"
|
||||||
|
version = "2.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "der"
|
name = "der"
|
||||||
version = "0.7.10"
|
version = "0.7.10"
|
||||||
@@ -3956,6 +3994,16 @@ dependencies = [
|
|||||||
"ucd-trie",
|
"ucd-trie",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pharos"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414"
|
||||||
|
dependencies = [
|
||||||
|
"futures",
|
||||||
|
"rustc_version 0.4.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.10"
|
version = "1.1.10"
|
||||||
@@ -4467,11 +4515,16 @@ dependencies = [
|
|||||||
name = "revive-dt-common"
|
name = "revive-dt-common"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"alloy",
|
||||||
|
"alloy-primitives",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"clap",
|
||||||
"moka",
|
"moka",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"schemars 1.0.4",
|
||||||
"semver 1.0.26",
|
"semver 1.0.26",
|
||||||
"serde",
|
"serde",
|
||||||
|
"strum",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4503,6 +4556,7 @@ dependencies = [
|
|||||||
"alloy",
|
"alloy",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
"revive-dt-common",
|
||||||
"semver 1.0.26",
|
"semver 1.0.26",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -4571,6 +4625,8 @@ dependencies = [
|
|||||||
"revive-dt-node-interaction",
|
"revive-dt-node-interaction",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_with",
|
||||||
|
"serde_yaml_ng",
|
||||||
"sp-core",
|
"sp-core",
|
||||||
"sp-runtime",
|
"sp-runtime",
|
||||||
"temp-dir",
|
"temp-dir",
|
||||||
@@ -4584,6 +4640,8 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy",
|
"alloy",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"revive-common",
|
||||||
|
"revive-dt-format",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4760,6 +4818,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321"
|
checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"rustls-webpki",
|
"rustls-webpki",
|
||||||
"subtle",
|
"subtle",
|
||||||
@@ -5047,6 +5106,12 @@ dependencies = [
|
|||||||
"pest",
|
"pest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "send_wrapper"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.219"
|
version = "1.0.219"
|
||||||
@@ -5163,6 +5228,19 @@ dependencies = [
|
|||||||
"syn 2.0.101",
|
"syn 2.0.101",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_yaml_ng"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b4db627b98b36d4203a7b458cf3573730f2bb591b28871d916dfa9efabfd41f"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap 2.10.0",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
"unsafe-libyaml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serdect"
|
name = "serdect"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -6059,6 +6137,22 @@ dependencies = [
|
|||||||
"tokio-util",
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-tungstenite"
|
||||||
|
version = "0.26.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
"tungstenite",
|
||||||
|
"webpki-roots 0.26.11",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.15"
|
version = "0.7.15"
|
||||||
@@ -6273,6 +6367,25 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tungstenite"
|
||||||
|
version = "0.26.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"data-encoding",
|
||||||
|
"http",
|
||||||
|
"httparse",
|
||||||
|
"log",
|
||||||
|
"rand 0.9.2",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"sha1",
|
||||||
|
"thiserror 2.0.12",
|
||||||
|
"utf-8",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tuplex"
|
name = "tuplex"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -6360,6 +6473,12 @@ version = "0.2.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unsafe-libyaml"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -6377,6 +6496,12 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf-8"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8_iter"
|
name = "utf8_iter"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@@ -6631,6 +6756,24 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "0.26.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
|
||||||
|
dependencies = [
|
||||||
|
"webpki-roots 1.0.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
|
||||||
|
dependencies = [
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "widestring"
|
name = "widestring"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -6969,6 +7112,25 @@ version = "0.6.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
|
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ws_stream_wasm"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc"
|
||||||
|
dependencies = [
|
||||||
|
"async_io_stream",
|
||||||
|
"futures",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"pharos",
|
||||||
|
"rustc_version 0.4.1",
|
||||||
|
"send_wrapper",
|
||||||
|
"thiserror 2.0.12",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wyz"
|
name = "wyz"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|||||||
+3
-1
@@ -45,7 +45,8 @@ serde_json = { version = "1.0", default-features = false, features = [
|
|||||||
"std",
|
"std",
|
||||||
"unbounded_depth",
|
"unbounded_depth",
|
||||||
] }
|
] }
|
||||||
serde_with = { version = "3.14.0" }
|
serde_with = { version = "3.14.0", features = ["hex"] }
|
||||||
|
serde_yaml_ng = { version = "0.10.0" }
|
||||||
sha2 = { version = "0.10.9" }
|
sha2 = { version = "0.10.9" }
|
||||||
sp-core = "36.1.0"
|
sp-core = "36.1.0"
|
||||||
sp-runtime = "41.1.0"
|
sp-runtime = "41.1.0"
|
||||||
@@ -80,6 +81,7 @@ features = [
|
|||||||
"json-abi",
|
"json-abi",
|
||||||
"providers",
|
"providers",
|
||||||
"provider-ipc",
|
"provider-ipc",
|
||||||
|
"provider-ws",
|
||||||
"provider-debug-api",
|
"provider-debug-api",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rpc-types",
|
"rpc-types",
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ This section describes the required dependencies that this framework requires to
|
|||||||
- ETH-RPC - All communication with Kitchensink is done through the ETH RPC.
|
- ETH-RPC - All communication with Kitchensink is done through the ETH RPC.
|
||||||
- Solc - This is actually a transitive dependency, while this tool doesn't require solc as it downloads the versions that it requires, resolc requires that Solc is installed and available in the path.
|
- Solc - This is actually a transitive dependency, while this tool doesn't require solc as it downloads the versions that it requires, resolc requires that Solc is installed and available in the path.
|
||||||
- Resolc - This is required to compile the contracts to PolkaVM bytecode.
|
- Resolc - This is required to compile the contracts to PolkaVM bytecode.
|
||||||
|
- Kurtosis - The Kurtosis CLI tool is required for the production Ethereum mainnet-like node configuration with Geth as the execution layer and lighthouse as the consensus layer. Kurtosis also requires docker to be installed since it runs everything inside of docker containers.
|
||||||
|
|
||||||
All of the above need to be installed and available in the path in order for the tool to work.
|
All of the above need to be installed and available in the path in order for the tool to work.
|
||||||
|
|
||||||
@@ -52,122 +53,152 @@ All of the above need to be installed and available in the path in order for the
|
|||||||
This tool is being updated quite frequently. Therefore, it's recommended that you don't install the tool and then run it, but rather that you run it from the root of the directory using `cargo run --release`. The help command of the tool gives you all of the information you need to know about each of the options and flags that the tool offers.
|
This tool is being updated quite frequently. Therefore, it's recommended that you don't install the tool and then run it, but rather that you run it from the root of the directory using `cargo run --release`. The help command of the tool gives you all of the information you need to know about each of the options and flags that the tool offers.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cargo run --release -- --help
|
$ cargo run --release -- execute-tests --help
|
||||||
Usage: retester [OPTIONS]
|
Error: Executes tests in the MatterLabs format differentially on multiple targets concurrently
|
||||||
|
|
||||||
|
Usage: retester execute-tests [OPTIONS]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-s, --solc <SOLC>
|
-w, --working-directory <WORKING_DIRECTORY>
|
||||||
The `solc` version to use if the test didn't specify it explicitly
|
The working directory that the program will use for all of the temporary artifacts needed at runtime.
|
||||||
|
|
||||||
[default: 0.8.29]
|
If not specified, then a temporary directory will be created and used by the program for all temporary artifacts.
|
||||||
|
|
||||||
--wasm
|
[default: ]
|
||||||
Use the Wasm compiler versions
|
|
||||||
|
|
||||||
-r, --resolc <RESOLC>
|
-p, --platform <PLATFORMS>
|
||||||
The path to the `resolc` executable to be tested.
|
The set of platforms that the differential tests should run on
|
||||||
|
|
||||||
By default it uses the `resolc` binary found in `$PATH`.
|
[default: geth-evm-solc,revive-dev-node-polkavm-resolc]
|
||||||
|
|
||||||
If `--wasm` is set, this should point to the resolc Wasm ile.
|
Possible values:
|
||||||
|
- geth-evm-solc: The Go-ethereum reference full node EVM implementation with the solc compiler
|
||||||
[default: resolc]
|
- kitchensink-polkavm-resolc: The kitchensink node with the PolkaVM backend with the resolc compiler
|
||||||
|
- kitchensink-revm-solc: The kitchensink node with the REVM backend with the solc compiler
|
||||||
|
- revive-dev-node-polkavm-resolc: The revive dev node with the PolkaVM backend with the resolc compiler
|
||||||
|
- revive-dev-node-revm-solc: The revive dev node with the REVM backend with the solc compiler
|
||||||
|
|
||||||
-c, --corpus <CORPUS>
|
-c, --corpus <CORPUS>
|
||||||
A list of test corpus JSON files to be tested
|
A list of test corpus JSON files to be tested
|
||||||
|
|
||||||
-w, --workdir <WORKING_DIRECTORY>
|
-h, --help
|
||||||
A place to store temporary artifacts during test execution.
|
Print help (see a summary with '-h')
|
||||||
|
|
||||||
Creates a temporary dir if not specified.
|
Solc Configuration:
|
||||||
|
--solc.version <VERSION>
|
||||||
|
Specifies the default version of the Solc compiler that should be used if there is no override specified by one of the test cases
|
||||||
|
|
||||||
-g, --geth <GETH>
|
[default: 0.8.29]
|
||||||
The path to the `geth` executable.
|
|
||||||
|
|
||||||
By default it uses `geth` binary found in `$PATH`.
|
Resolc Configuration:
|
||||||
|
--resolc.path <resolc.path>
|
||||||
|
Specifies the path of the resolc compiler to be used by the tool.
|
||||||
|
|
||||||
|
If this is not specified, then the tool assumes that it should use the resolc binary that's provided in the user's $PATH.
|
||||||
|
|
||||||
|
[default: resolc]
|
||||||
|
|
||||||
|
Geth Configuration:
|
||||||
|
--geth.path <geth.path>
|
||||||
|
Specifies the path of the geth node to be used by the tool.
|
||||||
|
|
||||||
|
If this is not specified, then the tool assumes that it should use the geth binary that's provided in the user's $PATH.
|
||||||
|
|
||||||
[default: geth]
|
[default: geth]
|
||||||
|
|
||||||
--geth-start-timeout <GETH_START_TIMEOUT>
|
--geth.start-timeout-ms <geth.start-timeout-ms>
|
||||||
The maximum time in milliseconds to wait for geth to start
|
The amount of time to wait upon startup before considering that the node timed out
|
||||||
|
|
||||||
[default: 5000]
|
[default: 5000]
|
||||||
|
|
||||||
--genesis <GENESIS_FILE>
|
Kitchensink Configuration:
|
||||||
Configure nodes according to this genesis.json file
|
--kitchensink.path <kitchensink.path>
|
||||||
|
Specifies the path of the kitchensink node to be used by the tool.
|
||||||
|
|
||||||
[default: genesis.json]
|
If this is not specified, then the tool assumes that it should use the kitchensink binary that's provided in the user's $PATH.
|
||||||
|
|
||||||
-a, --account <ACCOUNT>
|
[default: substrate-node]
|
||||||
The signing account private key
|
|
||||||
|
--kitchensink.start-timeout-ms <kitchensink.start-timeout-ms>
|
||||||
|
The amount of time to wait upon startup before considering that the node timed out
|
||||||
|
|
||||||
|
[default: 5000]
|
||||||
|
|
||||||
|
--kitchensink.dont-use-dev-node
|
||||||
|
This configures the tool to use Kitchensink instead of using the revive-dev-node
|
||||||
|
|
||||||
|
Revive Dev Node Configuration:
|
||||||
|
--revive-dev-node.path <revive-dev-node.path>
|
||||||
|
Specifies the path of the revive dev node to be used by the tool.
|
||||||
|
|
||||||
|
If this is not specified, then the tool assumes that it should use the revive dev node binary that's provided in the user's $PATH.
|
||||||
|
|
||||||
|
[default: revive-dev-node]
|
||||||
|
|
||||||
|
--revive-dev-node.start-timeout-ms <revive-dev-node.start-timeout-ms>
|
||||||
|
The amount of time to wait upon startup before considering that the node timed out
|
||||||
|
|
||||||
|
[default: 5000]
|
||||||
|
|
||||||
|
Eth RPC Configuration:
|
||||||
|
--eth-rpc.path <eth-rpc.path>
|
||||||
|
Specifies the path of the ETH RPC to be used by the tool.
|
||||||
|
|
||||||
|
If this is not specified, then the tool assumes that it should use the ETH RPC binary that's provided in the user's $PATH.
|
||||||
|
|
||||||
|
[default: eth-rpc]
|
||||||
|
|
||||||
|
--eth-rpc.start-timeout-ms <eth-rpc.start-timeout-ms>
|
||||||
|
The amount of time to wait upon startup before considering that the node timed out
|
||||||
|
|
||||||
|
[default: 5000]
|
||||||
|
|
||||||
|
Genesis Configuration:
|
||||||
|
--genesis.path <genesis.path>
|
||||||
|
Specifies the path of the genesis file to use for the nodes that are started.
|
||||||
|
|
||||||
|
This is expected to be the path of a JSON geth genesis file.
|
||||||
|
|
||||||
|
Wallet Configuration:
|
||||||
|
--wallet.default-private-key <DEFAULT_KEY>
|
||||||
|
The private key of the default signer
|
||||||
|
|
||||||
[default: 0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d]
|
[default: 0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d]
|
||||||
|
|
||||||
--private-keys-count <PRIVATE_KEYS_TO_ADD>
|
--wallet.additional-keys <ADDITIONAL_KEYS>
|
||||||
This argument controls which private keys the nodes should have access to and be added to its wallet signers. With a value of N, private keys (0, N] will be added to the signer set of the node
|
This argument controls which private keys the nodes should have access to and be added to its wallet signers. With a value of N, private keys (0, N] will be added to the signer set of the node
|
||||||
|
|
||||||
[default: 100000]
|
[default: 100000]
|
||||||
|
|
||||||
-l, --leader <LEADER>
|
Concurrency Configuration:
|
||||||
The differential testing leader node implementation
|
--concurrency.number-of-nodes <NUMBER_OF_NODES>
|
||||||
|
|
||||||
[default: geth]
|
|
||||||
|
|
||||||
Possible values:
|
|
||||||
- geth: The go-ethereum reference full node EVM implementation
|
|
||||||
- kitchensink: The kitchensink runtime provides the PolkaVM (PVM) based node implentation
|
|
||||||
|
|
||||||
-f, --follower <FOLLOWER>
|
|
||||||
The differential testing follower node implementation
|
|
||||||
|
|
||||||
[default: kitchensink]
|
|
||||||
|
|
||||||
Possible values:
|
|
||||||
- geth: The go-ethereum reference full node EVM implementation
|
|
||||||
- kitchensink: The kitchensink runtime provides the PolkaVM (PVM) based node implentation
|
|
||||||
|
|
||||||
--compile-only <COMPILE_ONLY>
|
|
||||||
Only compile against this testing platform (doesn't execute the tests)
|
|
||||||
|
|
||||||
Possible values:
|
|
||||||
- geth: The go-ethereum reference full node EVM implementation
|
|
||||||
- kitchensink: The kitchensink runtime provides the PolkaVM (PVM) based node implentation
|
|
||||||
|
|
||||||
--number-of-nodes <NUMBER_OF_NODES>
|
|
||||||
Determines the amount of nodes that will be spawned for each chain
|
Determines the amount of nodes that will be spawned for each chain
|
||||||
|
|
||||||
[default: 1]
|
[default: 5]
|
||||||
|
|
||||||
--number-of-threads <NUMBER_OF_THREADS>
|
--concurrency.number-of-threads <NUMBER_OF_THREADS>
|
||||||
Determines the amount of tokio worker threads that will will be used
|
Determines the amount of tokio worker threads that will will be used
|
||||||
|
|
||||||
[default: 16]
|
[default: 16]
|
||||||
|
|
||||||
--number-concurrent-tasks <NUMBER_CONCURRENT_TASKS>
|
--concurrency.number-of-concurrent-tasks <NUMBER_CONCURRENT_TASKS>
|
||||||
Determines the amount of concurrent tasks that will be spawned to run tests. Defaults to 10 x the number of nodes
|
Determines the amount of concurrent tasks that will be spawned to run tests.
|
||||||
|
|
||||||
-e, --extract-problems
|
Defaults to 10 x the number of nodes.
|
||||||
Extract problems back to the test corpus
|
|
||||||
|
|
||||||
-k, --kitchensink <KITCHENSINK>
|
--concurrency.ignore-concurrency-limit
|
||||||
The path to the `kitchensink` executable.
|
Determines if the concurrency limit should be ignored or not
|
||||||
|
|
||||||
By default it uses `substrate-node` binary found in `$PATH`.
|
Compilation Configuration:
|
||||||
|
--compilation.invalidate-cache
|
||||||
[default: substrate-node]
|
|
||||||
|
|
||||||
-p, --eth_proxy <ETH_PROXY>
|
|
||||||
The path to the `eth_proxy` executable.
|
|
||||||
|
|
||||||
By default it uses `eth-rpc` binary found in `$PATH`.
|
|
||||||
|
|
||||||
[default: eth-rpc]
|
|
||||||
|
|
||||||
-i, --invalidate-compilation-cache
|
|
||||||
Controls if the compilation cache should be invalidated or not
|
Controls if the compilation cache should be invalidated or not
|
||||||
|
|
||||||
-h, --help
|
Report Configuration:
|
||||||
Print help (see a summary with '-h')
|
--report.include-compiler-input
|
||||||
|
Controls if the compiler input is included in the final report
|
||||||
|
|
||||||
|
--report.include-compiler-output
|
||||||
|
Controls if the compiler output is included in the final report
|
||||||
```
|
```
|
||||||
|
|
||||||
To run tests with this tool you need a corpus JSON file that defines the tests included in the corpus. The simplest corpus file looks like the following:
|
To run tests with this tool you need a corpus JSON file that defines the tests included in the corpus. The simplest corpus file looks like the following:
|
||||||
@@ -188,10 +219,11 @@ The simplest command to run this tool is the following:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
RUST_LOG="info" cargo run --release -- execute-tests \
|
RUST_LOG="info" cargo run --release -- execute-tests \
|
||||||
--follower geth \
|
--platform geth-evm-solc \
|
||||||
--corpus path_to_your_corpus_file.json \
|
--corpus corp.json \
|
||||||
--working-directory path_to_a_temporary_directory_to_cache_things_in \
|
--working-directory workdir \
|
||||||
--concurrency.number-of-nodes 5 \
|
--concurrency.number-of-nodes 5 \
|
||||||
|
--concurrency.ignore-concurrency-limit \
|
||||||
> logs.log \
|
> logs.log \
|
||||||
2> output.log
|
2> output.log
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -9,11 +9,16 @@ repository.workspace = true
|
|||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
alloy = { workspace = true }
|
||||||
|
alloy-primitives = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
clap = { workspace = true }
|
||||||
moka = { workspace = true, features = ["sync"] }
|
moka = { workspace = true, features = ["sync"] }
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
semver = { workspace = true }
|
semver = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
schemars = { workspace = true }
|
||||||
|
strum = { workspace = true }
|
||||||
tokio = { workspace = true, default-features = false, features = ["time"] }
|
tokio = { workspace = true, default-features = false, features = ["time"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
use clap::ValueEnum;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use strum::{AsRefStr, Display, EnumString, IntoStaticStr};
|
||||||
|
|
||||||
|
/// An enum of the platform identifiers of all of the platforms supported by this framework. This
|
||||||
|
/// could be thought of like the target triple from Rust and LLVM where it specifies the platform
|
||||||
|
/// completely starting with the node, the vm, and finally the compiler used for this combination.
|
||||||
|
#[derive(
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
Debug,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
PartialOrd,
|
||||||
|
Ord,
|
||||||
|
Hash,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
ValueEnum,
|
||||||
|
EnumString,
|
||||||
|
Display,
|
||||||
|
AsRefStr,
|
||||||
|
IntoStaticStr,
|
||||||
|
JsonSchema,
|
||||||
|
)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[strum(serialize_all = "kebab-case")]
|
||||||
|
pub enum PlatformIdentifier {
|
||||||
|
/// The Go-ethereum reference full node EVM implementation with the solc compiler.
|
||||||
|
GethEvmSolc,
|
||||||
|
/// The Lighthouse Go-ethereum reference full node EVM implementation with the solc compiler.
|
||||||
|
LighthouseGethEvmSolc,
|
||||||
|
/// The kitchensink node with the PolkaVM backend with the resolc compiler.
|
||||||
|
KitchensinkPolkavmResolc,
|
||||||
|
/// The kitchensink node with the REVM backend with the solc compiler.
|
||||||
|
KitchensinkRevmSolc,
|
||||||
|
/// The revive dev node with the PolkaVM backend with the resolc compiler.
|
||||||
|
ReviveDevNodePolkavmResolc,
|
||||||
|
/// The revive dev node with the REVM backend with the solc compiler.
|
||||||
|
ReviveDevNodeRevmSolc,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An enum of the platform identifiers of all of the platforms supported by this framework.
|
||||||
|
#[derive(
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
Debug,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
PartialOrd,
|
||||||
|
Ord,
|
||||||
|
Hash,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
ValueEnum,
|
||||||
|
EnumString,
|
||||||
|
Display,
|
||||||
|
AsRefStr,
|
||||||
|
IntoStaticStr,
|
||||||
|
JsonSchema,
|
||||||
|
)]
|
||||||
|
pub enum CompilerIdentifier {
|
||||||
|
/// The solc compiler.
|
||||||
|
Solc,
|
||||||
|
/// The resolc compiler.
|
||||||
|
Resolc,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An enum representing the identifiers of the supported nodes.
|
||||||
|
#[derive(
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
Debug,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
PartialOrd,
|
||||||
|
Ord,
|
||||||
|
Hash,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
ValueEnum,
|
||||||
|
EnumString,
|
||||||
|
Display,
|
||||||
|
AsRefStr,
|
||||||
|
IntoStaticStr,
|
||||||
|
JsonSchema,
|
||||||
|
)]
|
||||||
|
pub enum NodeIdentifier {
|
||||||
|
/// The go-ethereum node implementation.
|
||||||
|
Geth,
|
||||||
|
/// The go-ethereum node implementation.
|
||||||
|
LighthouseGeth,
|
||||||
|
/// The Kitchensink node implementation.
|
||||||
|
Kitchensink,
|
||||||
|
/// The revive dev node implementation.
|
||||||
|
ReviveDevNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An enum representing the identifiers of the supported VMs.
|
||||||
|
#[derive(
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
Debug,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
PartialOrd,
|
||||||
|
Ord,
|
||||||
|
Hash,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
ValueEnum,
|
||||||
|
EnumString,
|
||||||
|
Display,
|
||||||
|
AsRefStr,
|
||||||
|
IntoStaticStr,
|
||||||
|
JsonSchema,
|
||||||
|
)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
#[strum(serialize_all = "lowercase")]
|
||||||
|
pub enum VmIdentifier {
|
||||||
|
/// The ethereum virtual machine.
|
||||||
|
Evm,
|
||||||
|
/// The EraVM virtual machine.
|
||||||
|
EraVM,
|
||||||
|
/// Polkadot's PolaVM Risc-v based virtual machine.
|
||||||
|
PolkaVM,
|
||||||
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
|
mod identifiers;
|
||||||
mod mode;
|
mod mode;
|
||||||
|
mod private_key_allocator;
|
||||||
mod version_or_requirement;
|
mod version_or_requirement;
|
||||||
|
|
||||||
|
pub use identifiers::*;
|
||||||
pub use mode::*;
|
pub use mode::*;
|
||||||
|
pub use private_key_allocator::*;
|
||||||
pub use version_or_requirement::*;
|
pub use version_or_requirement::*;
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use alloy::signers::local::PrivateKeySigner;
|
||||||
|
use alloy_primitives::U256;
|
||||||
|
use anyhow::{Result, bail};
|
||||||
|
|
||||||
|
/// This is a sequential private key allocator. When instantiated, it allocated private keys in
|
||||||
|
/// sequentially and in order until the maximum private key specified is reached.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct PrivateKeyAllocator {
|
||||||
|
/// The next private key to be returned by the allocator when requested.
|
||||||
|
next_private_key: U256,
|
||||||
|
|
||||||
|
/// The highest private key (exclusive) that can be returned by this allocator.
|
||||||
|
highest_private_key_exclusive: U256,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrivateKeyAllocator {
|
||||||
|
/// Creates a new instance of the private key allocator.
|
||||||
|
pub fn new(highest_private_key_exclusive: U256) -> Self {
|
||||||
|
Self {
|
||||||
|
next_private_key: U256::ZERO,
|
||||||
|
highest_private_key_exclusive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocates a new private key and errors out if the maximum private key has been reached.
|
||||||
|
pub fn allocate(&mut self) -> Result<PrivateKeySigner> {
|
||||||
|
if self.next_private_key >= self.highest_private_key_exclusive {
|
||||||
|
bail!("Attempted to allocate a private key but failed since all have been allocated");
|
||||||
|
};
|
||||||
|
let private_key =
|
||||||
|
PrivateKeySigner::from_slice(self.next_private_key.to_be_bytes::<32>().as_slice())?;
|
||||||
|
self.next_private_key += U256::ONE;
|
||||||
|
Ok(private_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ use std::{
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
pin::Pin,
|
||||||
};
|
};
|
||||||
|
|
||||||
use alloy::json_abi::JsonAbi;
|
use alloy::json_abi::JsonAbi;
|
||||||
@@ -17,8 +18,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use revive_common::EVMVersion;
|
use revive_common::EVMVersion;
|
||||||
use revive_dt_common::cached_fs::read_to_string;
|
use revive_dt_common::cached_fs::read_to_string;
|
||||||
use revive_dt_common::types::VersionOrRequirement;
|
|
||||||
use revive_dt_config::{ResolcConfiguration, SolcConfiguration, WorkingDirectoryConfiguration};
|
|
||||||
|
|
||||||
// Re-export this as it's a part of the compiler interface.
|
// Re-export this as it's a part of the compiler interface.
|
||||||
pub use revive_dt_common::types::{Mode, ModeOptimizerSetting, ModePipeline};
|
pub use revive_dt_common::types::{Mode, ModeOptimizerSetting, ModePipeline};
|
||||||
@@ -28,19 +27,7 @@ pub mod revive_resolc;
|
|||||||
pub mod solc;
|
pub mod solc;
|
||||||
|
|
||||||
/// A common interface for all supported Solidity compilers.
|
/// A common interface for all supported Solidity compilers.
|
||||||
pub trait SolidityCompiler: Sized {
|
pub trait SolidityCompiler {
|
||||||
/// Instantiates a new compiler object.
|
|
||||||
///
|
|
||||||
/// Based on the given [`Context`] and [`VersionOrRequirement`] this function instantiates a
|
|
||||||
/// new compiler object. Certain implementations of this trait might choose to cache cache the
|
|
||||||
/// compiler objects and return the same ones over and over again.
|
|
||||||
fn new(
|
|
||||||
context: impl AsRef<SolcConfiguration>
|
|
||||||
+ AsRef<ResolcConfiguration>
|
|
||||||
+ AsRef<WorkingDirectoryConfiguration>,
|
|
||||||
version: impl Into<Option<VersionOrRequirement>>,
|
|
||||||
) -> impl Future<Output = Result<Self>>;
|
|
||||||
|
|
||||||
/// Returns the version of the compiler.
|
/// Returns the version of the compiler.
|
||||||
fn version(&self) -> &Version;
|
fn version(&self) -> &Version;
|
||||||
|
|
||||||
@@ -48,7 +35,10 @@ pub trait SolidityCompiler: Sized {
|
|||||||
fn path(&self) -> &Path;
|
fn path(&self) -> &Path;
|
||||||
|
|
||||||
/// The low-level compiler interface.
|
/// The low-level compiler interface.
|
||||||
fn build(&self, input: CompilerInput) -> impl Future<Output = Result<CompilerOutput>>;
|
fn build(
|
||||||
|
&self,
|
||||||
|
input: CompilerInput,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<CompilerOutput>> + '_>>;
|
||||||
|
|
||||||
/// Does the compiler support the provided mode and version settings.
|
/// Does the compiler support the provided mode and version settings.
|
||||||
fn supports_mode(
|
fn supports_mode(
|
||||||
@@ -74,7 +64,7 @@ pub struct CompilerInput {
|
|||||||
/// The generic compilation output configuration.
|
/// The generic compilation output configuration.
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
pub struct CompilerOutput {
|
pub struct CompilerOutput {
|
||||||
/// The compiled contracts. The bytecode of the contract is kept as a string incase linking is
|
/// The compiled contracts. The bytecode of the contract is kept as a string in case linking is
|
||||||
/// required and the compiled source has placeholders.
|
/// required and the compiled source has placeholders.
|
||||||
pub contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
pub contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
||||||
}
|
}
|
||||||
@@ -164,7 +154,7 @@ impl Compiler {
|
|||||||
callback(self)
|
callback(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn try_build(self, compiler: &impl SolidityCompiler) -> Result<CompilerOutput> {
|
pub async fn try_build(self, compiler: &dyn SolidityCompiler) -> Result<CompilerOutput> {
|
||||||
compiler.build(self.input).await
|
compiler.build(self.input).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
pin::Pin,
|
||||||
process::Stdio,
|
process::Stdio,
|
||||||
sync::{Arc, LazyLock},
|
sync::{Arc, LazyLock},
|
||||||
};
|
};
|
||||||
@@ -37,8 +38,8 @@ struct ResolcInner {
|
|||||||
resolc_path: PathBuf,
|
resolc_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SolidityCompiler for Resolc {
|
impl Resolc {
|
||||||
async fn new(
|
pub async fn new(
|
||||||
context: impl AsRef<SolcConfiguration>
|
context: impl AsRef<SolcConfiguration>
|
||||||
+ AsRef<ResolcConfiguration>
|
+ AsRef<ResolcConfiguration>
|
||||||
+ AsRef<WorkingDirectoryConfiguration>,
|
+ AsRef<WorkingDirectoryConfiguration>,
|
||||||
@@ -65,11 +66,13 @@ impl SolidityCompiler for Resolc {
|
|||||||
})
|
})
|
||||||
.clone())
|
.clone())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SolidityCompiler for Resolc {
|
||||||
fn version(&self) -> &Version {
|
fn version(&self) -> &Version {
|
||||||
// We currently return the solc compiler version since we do not support multiple resolc
|
// We currently return the solc compiler version since we do not support multiple resolc
|
||||||
// compiler versions.
|
// compiler versions.
|
||||||
self.0.solc.version()
|
SolidityCompiler::version(&self.0.solc)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path(&self) -> &std::path::Path {
|
fn path(&self) -> &std::path::Path {
|
||||||
@@ -77,7 +80,7 @@ impl SolidityCompiler for Resolc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", ret)]
|
#[tracing::instrument(level = "debug", ret)]
|
||||||
async fn build(
|
fn build(
|
||||||
&self,
|
&self,
|
||||||
CompilerInput {
|
CompilerInput {
|
||||||
pipeline,
|
pipeline,
|
||||||
@@ -91,7 +94,8 @@ impl SolidityCompiler for Resolc {
|
|||||||
// resolc. So, we need to go back to this later once it's supported.
|
// resolc. So, we need to go back to this later once it's supported.
|
||||||
revert_string_handling: _,
|
revert_string_handling: _,
|
||||||
}: CompilerInput,
|
}: CompilerInput,
|
||||||
) -> Result<CompilerOutput> {
|
) -> Pin<Box<dyn Future<Output = Result<CompilerOutput>> + '_>> {
|
||||||
|
Box::pin(async move {
|
||||||
if !matches!(pipeline, None | Some(ModePipeline::ViaYulIR)) {
|
if !matches!(pipeline, None | Some(ModePipeline::ViaYulIR)) {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"Resolc only supports the Y (via Yul IR) pipeline, but the provided pipeline is {pipeline:?}"
|
"Resolc only supports the Y (via Yul IR) pipeline, but the provided pipeline is {pipeline:?}"
|
||||||
@@ -138,7 +142,8 @@ impl SolidityCompiler for Resolc {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut command = AsyncCommand::new(self.path());
|
let path = &self.0.resolc_path;
|
||||||
|
let mut command = AsyncCommand::new(path);
|
||||||
command
|
command
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
@@ -159,7 +164,7 @@ impl SolidityCompiler for Resolc {
|
|||||||
}
|
}
|
||||||
let mut child = command
|
let mut child = command
|
||||||
.spawn()
|
.spawn()
|
||||||
.with_context(|| format!("Failed to spawn resolc at {}", self.path().display()))?;
|
.with_context(|| format!("Failed to spawn resolc at {}", path.display()))?;
|
||||||
|
|
||||||
let stdin_pipe = child.stdin.as_mut().expect("stdin must be piped");
|
let stdin_pipe = child.stdin.as_mut().expect("stdin must be piped");
|
||||||
let serialized_input = serde_json::to_vec(&input)
|
let serialized_input = serde_json::to_vec(&input)
|
||||||
@@ -240,7 +245,9 @@ impl SolidityCompiler for Resolc {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.context("No metadata found for the contract")?;
|
.context("No metadata found for the contract")?;
|
||||||
let solc_metadata_str = match metadata {
|
let solc_metadata_str = match metadata {
|
||||||
serde_json::Value::String(solc_metadata_str) => solc_metadata_str.as_str(),
|
serde_json::Value::String(solc_metadata_str) => {
|
||||||
|
solc_metadata_str.as_str()
|
||||||
|
}
|
||||||
serde_json::Value::Object(metadata_object) => {
|
serde_json::Value::Object(metadata_object) => {
|
||||||
let solc_metadata_value = metadata_object
|
let solc_metadata_value = metadata_object
|
||||||
.get("solc_metadata")
|
.get("solc_metadata")
|
||||||
@@ -256,8 +263,10 @@ impl SolidityCompiler for Resolc {
|
|||||||
anyhow::bail!("Unsupported type of metadata {metadata:?}")
|
anyhow::bail!("Unsupported type of metadata {metadata:?}")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let solc_metadata =
|
let solc_metadata = serde_json::from_str::<serde_json::Value>(
|
||||||
serde_json::from_str::<serde_json::Value>(solc_metadata_str).context(
|
solc_metadata_str,
|
||||||
|
)
|
||||||
|
.context(
|
||||||
"Failed to deserialize the solc_metadata as a serde_json generic value",
|
"Failed to deserialize the solc_metadata as a serde_json generic value",
|
||||||
)?;
|
)?;
|
||||||
let output_value = solc_metadata
|
let output_value = solc_metadata
|
||||||
@@ -274,6 +283,7 @@ impl SolidityCompiler for Resolc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(compiler_output)
|
Ok(compiler_output)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports_mode(
|
fn supports_mode(
|
||||||
@@ -281,6 +291,7 @@ impl SolidityCompiler for Resolc {
|
|||||||
optimize_setting: ModeOptimizerSetting,
|
optimize_setting: ModeOptimizerSetting,
|
||||||
pipeline: ModePipeline,
|
pipeline: ModePipeline,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
pipeline == ModePipeline::ViaYulIR && self.0.solc.supports_mode(optimize_setting, pipeline)
|
pipeline == ModePipeline::ViaYulIR
|
||||||
|
&& SolidityCompiler::supports_mode(&self.0.solc, optimize_setting, pipeline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
pin::Pin,
|
||||||
process::Stdio,
|
process::Stdio,
|
||||||
sync::{Arc, LazyLock},
|
sync::{Arc, LazyLock},
|
||||||
};
|
};
|
||||||
@@ -36,8 +37,8 @@ struct SolcInner {
|
|||||||
solc_version: Version,
|
solc_version: Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SolidityCompiler for Solc {
|
impl Solc {
|
||||||
async fn new(
|
pub async fn new(
|
||||||
context: impl AsRef<SolcConfiguration>
|
context: impl AsRef<SolcConfiguration>
|
||||||
+ AsRef<ResolcConfiguration>
|
+ AsRef<ResolcConfiguration>
|
||||||
+ AsRef<WorkingDirectoryConfiguration>,
|
+ AsRef<WorkingDirectoryConfiguration>,
|
||||||
@@ -75,7 +76,9 @@ impl SolidityCompiler for Solc {
|
|||||||
})
|
})
|
||||||
.clone())
|
.clone())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SolidityCompiler for Solc {
|
||||||
fn version(&self) -> &Version {
|
fn version(&self) -> &Version {
|
||||||
&self.0.solc_version
|
&self.0.solc_version
|
||||||
}
|
}
|
||||||
@@ -85,7 +88,7 @@ impl SolidityCompiler for Solc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", ret)]
|
#[tracing::instrument(level = "debug", ret)]
|
||||||
async fn build(
|
fn build(
|
||||||
&self,
|
&self,
|
||||||
CompilerInput {
|
CompilerInput {
|
||||||
pipeline,
|
pipeline,
|
||||||
@@ -97,7 +100,8 @@ impl SolidityCompiler for Solc {
|
|||||||
libraries,
|
libraries,
|
||||||
revert_string_handling,
|
revert_string_handling,
|
||||||
}: CompilerInput,
|
}: CompilerInput,
|
||||||
) -> Result<CompilerOutput> {
|
) -> Pin<Box<dyn Future<Output = Result<CompilerOutput>> + '_>> {
|
||||||
|
Box::pin(async move {
|
||||||
// Be careful to entirely omit the viaIR field if the compiler does not support it,
|
// Be careful to entirely omit the viaIR field if the compiler does not support it,
|
||||||
// as it will error if you provide fields it does not know about. Because
|
// as it will error if you provide fields it does not know about. Because
|
||||||
// `supports_mode` is called prior to instantiating a compiler, we should never
|
// `supports_mode` is called prior to instantiating a compiler, we should never
|
||||||
@@ -162,7 +166,8 @@ impl SolidityCompiler for Solc {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut command = AsyncCommand::new(self.path());
|
let path = &self.0.solc_path;
|
||||||
|
let mut command = AsyncCommand::new(path);
|
||||||
command
|
command
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
@@ -183,7 +188,7 @@ impl SolidityCompiler for Solc {
|
|||||||
}
|
}
|
||||||
let mut child = command
|
let mut child = command
|
||||||
.spawn()
|
.spawn()
|
||||||
.with_context(|| format!("Failed to spawn solc at {}", self.path().display()))?;
|
.with_context(|| format!("Failed to spawn solc at {}", path.display()))?;
|
||||||
|
|
||||||
let stdin = child.stdin.as_mut().expect("should be piped");
|
let stdin = child.stdin.as_mut().expect("should be piped");
|
||||||
let serialized_input = serde_json::to_vec(&input)
|
let serialized_input = serde_json::to_vec(&input)
|
||||||
@@ -261,6 +266,7 @@ impl SolidityCompiler for Solc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(compiler_output)
|
Ok(compiler_output)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports_mode(
|
fn supports_mode(
|
||||||
@@ -278,6 +284,6 @@ impl SolidityCompiler for Solc {
|
|||||||
impl Solc {
|
impl Solc {
|
||||||
fn compiler_supports_yul(&self) -> bool {
|
fn compiler_supports_yul(&self) -> bool {
|
||||||
const SOLC_VERSION_SUPPORTING_VIA_YUL_IR: Version = Version::new(0, 8, 13);
|
const SOLC_VERSION_SUPPORTING_VIA_YUL_IR: Version = Version::new(0, 8, 13);
|
||||||
self.version() >= &SOLC_VERSION_SUPPORTING_VIA_YUL_IR
|
SolidityCompiler::version(self) >= &SOLC_VERSION_SUPPORTING_VIA_YUL_IR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use revive_dt_common::types::VersionOrRequirement;
|
use revive_dt_common::types::VersionOrRequirement;
|
||||||
use revive_dt_compiler::{Compiler, SolidityCompiler, revive_resolc::Resolc, solc::Solc};
|
use revive_dt_compiler::{Compiler, revive_resolc::Resolc, solc::Solc};
|
||||||
use revive_dt_config::ExecutionContext;
|
use revive_dt_config::TestExecutionContext;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn contracts_can_be_compiled_with_solc() {
|
async fn contracts_can_be_compiled_with_solc() {
|
||||||
// Arrange
|
// Arrange
|
||||||
let args = ExecutionContext::default();
|
let args = TestExecutionContext::default();
|
||||||
let solc = Solc::new(&args, VersionOrRequirement::Version(Version::new(0, 8, 30)))
|
let solc = Solc::new(&args, VersionOrRequirement::Version(Version::new(0, 8, 30)))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -49,7 +49,7 @@ async fn contracts_can_be_compiled_with_solc() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn contracts_can_be_compiled_with_resolc() {
|
async fn contracts_can_be_compiled_with_resolc() {
|
||||||
// Arrange
|
// Arrange
|
||||||
let args = ExecutionContext::default();
|
let args = TestExecutionContext::default();
|
||||||
let resolc = Resolc::new(&args, VersionOrRequirement::Version(Version::new(0, 8, 30)))
|
let resolc = Resolc::new(&args, VersionOrRequirement::Version(Version::new(0, 8, 30)))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ repository.workspace = true
|
|||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
revive-dt-common = { workspace = true }
|
||||||
|
|
||||||
alloy = { workspace = true }
|
alloy = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
|
|||||||
+162
-33
@@ -18,6 +18,7 @@ use alloy::{
|
|||||||
signers::local::PrivateKeySigner,
|
signers::local::PrivateKeySigner,
|
||||||
};
|
};
|
||||||
use clap::{Parser, ValueEnum, ValueHint};
|
use clap::{Parser, ValueEnum, ValueHint};
|
||||||
|
use revive_dt_common::types::PlatformIdentifier;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
use strum::{AsRefStr, Display, EnumString, IntoStaticStr};
|
use strum::{AsRefStr, Display, EnumString, IntoStaticStr};
|
||||||
@@ -26,8 +27,8 @@ use temp_dir::TempDir;
|
|||||||
#[derive(Clone, Debug, Parser, Serialize)]
|
#[derive(Clone, Debug, Parser, Serialize)]
|
||||||
#[command(name = "retester")]
|
#[command(name = "retester")]
|
||||||
pub enum Context {
|
pub enum Context {
|
||||||
/// Executes tests in the MatterLabs format differentially against a leader and a follower.
|
/// Executes tests in the MatterLabs format differentially on multiple targets concurrently.
|
||||||
ExecuteTests(Box<ExecutionContext>),
|
ExecuteTests(Box<TestExecutionContext>),
|
||||||
/// Exports the JSON schema of the MatterLabs test format used by the tool.
|
/// Exports the JSON schema of the MatterLabs test format used by the tool.
|
||||||
ExportJsonSchema,
|
ExportJsonSchema,
|
||||||
}
|
}
|
||||||
@@ -45,8 +46,107 @@ impl Context {
|
|||||||
impl AsRef<WorkingDirectoryConfiguration> for Context {
|
impl AsRef<WorkingDirectoryConfiguration> for Context {
|
||||||
fn as_ref(&self) -> &WorkingDirectoryConfiguration {
|
fn as_ref(&self) -> &WorkingDirectoryConfiguration {
|
||||||
match self {
|
match self {
|
||||||
Context::ExecuteTests(execution_context) => &execution_context.working_directory,
|
Self::ExecuteTests(context) => context.as_ref().as_ref(),
|
||||||
Context::ExportJsonSchema => unreachable!(),
|
Self::ExportJsonSchema => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<SolcConfiguration> for Context {
|
||||||
|
fn as_ref(&self) -> &SolcConfiguration {
|
||||||
|
match self {
|
||||||
|
Self::ExecuteTests(context) => context.as_ref().as_ref(),
|
||||||
|
Self::ExportJsonSchema => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<ResolcConfiguration> for Context {
|
||||||
|
fn as_ref(&self) -> &ResolcConfiguration {
|
||||||
|
match self {
|
||||||
|
Self::ExecuteTests(context) => context.as_ref().as_ref(),
|
||||||
|
Self::ExportJsonSchema => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<GethConfiguration> for Context {
|
||||||
|
fn as_ref(&self) -> &GethConfiguration {
|
||||||
|
match self {
|
||||||
|
Self::ExecuteTests(context) => context.as_ref().as_ref(),
|
||||||
|
Self::ExportJsonSchema => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<KurtosisConfiguration> for Context {
|
||||||
|
fn as_ref(&self) -> &KurtosisConfiguration {
|
||||||
|
match self {
|
||||||
|
Self::ExecuteTests(context) => context.as_ref().as_ref(),
|
||||||
|
Self::ExportJsonSchema => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<KitchensinkConfiguration> for Context {
|
||||||
|
fn as_ref(&self) -> &KitchensinkConfiguration {
|
||||||
|
match self {
|
||||||
|
Self::ExecuteTests(context) => context.as_ref().as_ref(),
|
||||||
|
Self::ExportJsonSchema => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<ReviveDevNodeConfiguration> for Context {
|
||||||
|
fn as_ref(&self) -> &ReviveDevNodeConfiguration {
|
||||||
|
match self {
|
||||||
|
Self::ExecuteTests(context) => context.as_ref().as_ref(),
|
||||||
|
Self::ExportJsonSchema => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<EthRpcConfiguration> for Context {
|
||||||
|
fn as_ref(&self) -> &EthRpcConfiguration {
|
||||||
|
match self {
|
||||||
|
Self::ExecuteTests(context) => context.as_ref().as_ref(),
|
||||||
|
Self::ExportJsonSchema => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<GenesisConfiguration> for Context {
|
||||||
|
fn as_ref(&self) -> &GenesisConfiguration {
|
||||||
|
match self {
|
||||||
|
Self::ExecuteTests(context) => context.as_ref().as_ref(),
|
||||||
|
Self::ExportJsonSchema => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<WalletConfiguration> for Context {
|
||||||
|
fn as_ref(&self) -> &WalletConfiguration {
|
||||||
|
match self {
|
||||||
|
Self::ExecuteTests(context) => context.as_ref().as_ref(),
|
||||||
|
Self::ExportJsonSchema => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<ConcurrencyConfiguration> for Context {
|
||||||
|
fn as_ref(&self) -> &ConcurrencyConfiguration {
|
||||||
|
match self {
|
||||||
|
Self::ExecuteTests(context) => context.as_ref().as_ref(),
|
||||||
|
Self::ExportJsonSchema => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<CompilationConfiguration> for Context {
|
||||||
|
fn as_ref(&self) -> &CompilationConfiguration {
|
||||||
|
match self {
|
||||||
|
Self::ExecuteTests(context) => context.as_ref().as_ref(),
|
||||||
|
Self::ExportJsonSchema => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,14 +154,14 @@ impl AsRef<WorkingDirectoryConfiguration> for Context {
|
|||||||
impl AsRef<ReportConfiguration> for Context {
|
impl AsRef<ReportConfiguration> for Context {
|
||||||
fn as_ref(&self) -> &ReportConfiguration {
|
fn as_ref(&self) -> &ReportConfiguration {
|
||||||
match self {
|
match self {
|
||||||
Context::ExecuteTests(execution_context) => &execution_context.report_configuration,
|
Self::ExecuteTests(context) => context.as_ref().as_ref(),
|
||||||
Context::ExportJsonSchema => unreachable!(),
|
Self::ExportJsonSchema => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Parser, Serialize)]
|
#[derive(Clone, Debug, Parser, Serialize)]
|
||||||
pub struct ExecutionContext {
|
pub struct TestExecutionContext {
|
||||||
/// The working directory that the program will use for all of the temporary artifacts needed at
|
/// The working directory that the program will use for all of the temporary artifacts needed at
|
||||||
/// runtime.
|
/// runtime.
|
||||||
///
|
///
|
||||||
@@ -75,13 +175,13 @@ pub struct ExecutionContext {
|
|||||||
)]
|
)]
|
||||||
pub working_directory: WorkingDirectoryConfiguration,
|
pub working_directory: WorkingDirectoryConfiguration,
|
||||||
|
|
||||||
/// The differential testing leader node implementation.
|
/// The set of platforms that the differential tests should run on.
|
||||||
#[arg(short, long = "leader", default_value_t = TestingPlatform::Geth)]
|
#[arg(
|
||||||
pub leader: TestingPlatform,
|
short = 'p',
|
||||||
|
long = "platform",
|
||||||
/// The differential testing follower node implementation.
|
default_values = ["geth-evm-solc", "revive-dev-node-polkavm-resolc"]
|
||||||
#[arg(short, long = "follower", default_value_t = TestingPlatform::Kitchensink)]
|
)]
|
||||||
pub follower: TestingPlatform,
|
pub platforms: Vec<PlatformIdentifier>,
|
||||||
|
|
||||||
/// A list of test corpus JSON files to be tested.
|
/// A list of test corpus JSON files to be tested.
|
||||||
#[arg(long = "corpus", short)]
|
#[arg(long = "corpus", short)]
|
||||||
@@ -99,6 +199,10 @@ pub struct ExecutionContext {
|
|||||||
#[clap(flatten, next_help_heading = "Geth Configuration")]
|
#[clap(flatten, next_help_heading = "Geth Configuration")]
|
||||||
pub geth_configuration: GethConfiguration,
|
pub geth_configuration: GethConfiguration,
|
||||||
|
|
||||||
|
/// Configuration parameters for the lighthouse node.
|
||||||
|
#[clap(flatten, next_help_heading = "Lighthouse Configuration")]
|
||||||
|
pub lighthouse_configuration: KurtosisConfiguration,
|
||||||
|
|
||||||
/// Configuration parameters for the Kitchensink.
|
/// Configuration parameters for the Kitchensink.
|
||||||
#[clap(flatten, next_help_heading = "Kitchensink Configuration")]
|
#[clap(flatten, next_help_heading = "Kitchensink Configuration")]
|
||||||
pub kitchensink_configuration: KitchensinkConfiguration,
|
pub kitchensink_configuration: KitchensinkConfiguration,
|
||||||
@@ -132,79 +236,85 @@ pub struct ExecutionContext {
|
|||||||
pub report_configuration: ReportConfiguration,
|
pub report_configuration: ReportConfiguration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ExecutionContext {
|
impl Default for TestExecutionContext {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::parse_from(["execution-context"])
|
Self::parse_from(["execution-context"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<WorkingDirectoryConfiguration> for ExecutionContext {
|
impl AsRef<WorkingDirectoryConfiguration> for TestExecutionContext {
|
||||||
fn as_ref(&self) -> &WorkingDirectoryConfiguration {
|
fn as_ref(&self) -> &WorkingDirectoryConfiguration {
|
||||||
&self.working_directory
|
&self.working_directory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<SolcConfiguration> for ExecutionContext {
|
impl AsRef<SolcConfiguration> for TestExecutionContext {
|
||||||
fn as_ref(&self) -> &SolcConfiguration {
|
fn as_ref(&self) -> &SolcConfiguration {
|
||||||
&self.solc_configuration
|
&self.solc_configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<ResolcConfiguration> for ExecutionContext {
|
impl AsRef<ResolcConfiguration> for TestExecutionContext {
|
||||||
fn as_ref(&self) -> &ResolcConfiguration {
|
fn as_ref(&self) -> &ResolcConfiguration {
|
||||||
&self.resolc_configuration
|
&self.resolc_configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<GethConfiguration> for ExecutionContext {
|
impl AsRef<GethConfiguration> for TestExecutionContext {
|
||||||
fn as_ref(&self) -> &GethConfiguration {
|
fn as_ref(&self) -> &GethConfiguration {
|
||||||
&self.geth_configuration
|
&self.geth_configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<KitchensinkConfiguration> for ExecutionContext {
|
impl AsRef<KurtosisConfiguration> for TestExecutionContext {
|
||||||
|
fn as_ref(&self) -> &KurtosisConfiguration {
|
||||||
|
&self.lighthouse_configuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<KitchensinkConfiguration> for TestExecutionContext {
|
||||||
fn as_ref(&self) -> &KitchensinkConfiguration {
|
fn as_ref(&self) -> &KitchensinkConfiguration {
|
||||||
&self.kitchensink_configuration
|
&self.kitchensink_configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<ReviveDevNodeConfiguration> for ExecutionContext {
|
impl AsRef<ReviveDevNodeConfiguration> for TestExecutionContext {
|
||||||
fn as_ref(&self) -> &ReviveDevNodeConfiguration {
|
fn as_ref(&self) -> &ReviveDevNodeConfiguration {
|
||||||
&self.revive_dev_node_configuration
|
&self.revive_dev_node_configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<EthRpcConfiguration> for ExecutionContext {
|
impl AsRef<EthRpcConfiguration> for TestExecutionContext {
|
||||||
fn as_ref(&self) -> &EthRpcConfiguration {
|
fn as_ref(&self) -> &EthRpcConfiguration {
|
||||||
&self.eth_rpc_configuration
|
&self.eth_rpc_configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<GenesisConfiguration> for ExecutionContext {
|
impl AsRef<GenesisConfiguration> for TestExecutionContext {
|
||||||
fn as_ref(&self) -> &GenesisConfiguration {
|
fn as_ref(&self) -> &GenesisConfiguration {
|
||||||
&self.genesis_configuration
|
&self.genesis_configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<WalletConfiguration> for ExecutionContext {
|
impl AsRef<WalletConfiguration> for TestExecutionContext {
|
||||||
fn as_ref(&self) -> &WalletConfiguration {
|
fn as_ref(&self) -> &WalletConfiguration {
|
||||||
&self.wallet_configuration
|
&self.wallet_configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<ConcurrencyConfiguration> for ExecutionContext {
|
impl AsRef<ConcurrencyConfiguration> for TestExecutionContext {
|
||||||
fn as_ref(&self) -> &ConcurrencyConfiguration {
|
fn as_ref(&self) -> &ConcurrencyConfiguration {
|
||||||
&self.concurrency_configuration
|
&self.concurrency_configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<CompilationConfiguration> for ExecutionContext {
|
impl AsRef<CompilationConfiguration> for TestExecutionContext {
|
||||||
fn as_ref(&self) -> &CompilationConfiguration {
|
fn as_ref(&self) -> &CompilationConfiguration {
|
||||||
&self.compilation_configuration
|
&self.compilation_configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<ReportConfiguration> for ExecutionContext {
|
impl AsRef<ReportConfiguration> for TestExecutionContext {
|
||||||
fn as_ref(&self) -> &ReportConfiguration {
|
fn as_ref(&self) -> &ReportConfiguration {
|
||||||
&self.report_configuration
|
&self.report_configuration
|
||||||
}
|
}
|
||||||
@@ -244,12 +354,27 @@ pub struct GethConfiguration {
|
|||||||
#[clap(
|
#[clap(
|
||||||
id = "geth.start-timeout-ms",
|
id = "geth.start-timeout-ms",
|
||||||
long = "geth.start-timeout-ms",
|
long = "geth.start-timeout-ms",
|
||||||
default_value = "5000",
|
default_value = "30000",
|
||||||
value_parser = parse_duration
|
value_parser = parse_duration
|
||||||
)]
|
)]
|
||||||
pub start_timeout_ms: Duration,
|
pub start_timeout_ms: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A set of configuration parameters for kurtosis.
|
||||||
|
#[derive(Clone, Debug, Parser, Serialize)]
|
||||||
|
pub struct KurtosisConfiguration {
|
||||||
|
/// Specifies the path of the kurtosis node to be used by the tool.
|
||||||
|
///
|
||||||
|
/// If this is not specified, then the tool assumes that it should use the kurtosis binary that's
|
||||||
|
/// provided in the user's $PATH.
|
||||||
|
#[clap(
|
||||||
|
id = "kurtosis.path",
|
||||||
|
long = "kurtosis.path",
|
||||||
|
default_value = "kurtosis"
|
||||||
|
)]
|
||||||
|
pub path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
/// A set of configuration parameters for Kitchensink.
|
/// A set of configuration parameters for Kitchensink.
|
||||||
#[derive(Clone, Debug, Parser, Serialize)]
|
#[derive(Clone, Debug, Parser, Serialize)]
|
||||||
pub struct KitchensinkConfiguration {
|
pub struct KitchensinkConfiguration {
|
||||||
@@ -268,7 +393,7 @@ pub struct KitchensinkConfiguration {
|
|||||||
#[clap(
|
#[clap(
|
||||||
id = "kitchensink.start-timeout-ms",
|
id = "kitchensink.start-timeout-ms",
|
||||||
long = "kitchensink.start-timeout-ms",
|
long = "kitchensink.start-timeout-ms",
|
||||||
default_value = "5000",
|
default_value = "30000",
|
||||||
value_parser = parse_duration
|
value_parser = parse_duration
|
||||||
)]
|
)]
|
||||||
pub start_timeout_ms: Duration,
|
pub start_timeout_ms: Duration,
|
||||||
@@ -296,7 +421,7 @@ pub struct ReviveDevNodeConfiguration {
|
|||||||
#[clap(
|
#[clap(
|
||||||
id = "revive-dev-node.start-timeout-ms",
|
id = "revive-dev-node.start-timeout-ms",
|
||||||
long = "revive-dev-node.start-timeout-ms",
|
long = "revive-dev-node.start-timeout-ms",
|
||||||
default_value = "5000",
|
default_value = "30000",
|
||||||
value_parser = parse_duration
|
value_parser = parse_duration
|
||||||
)]
|
)]
|
||||||
pub start_timeout_ms: Duration,
|
pub start_timeout_ms: Duration,
|
||||||
@@ -316,7 +441,7 @@ pub struct EthRpcConfiguration {
|
|||||||
#[clap(
|
#[clap(
|
||||||
id = "eth-rpc.start-timeout-ms",
|
id = "eth-rpc.start-timeout-ms",
|
||||||
long = "eth-rpc.start-timeout-ms",
|
long = "eth-rpc.start-timeout-ms",
|
||||||
default_value = "5000",
|
default_value = "30000",
|
||||||
value_parser = parse_duration
|
value_parser = parse_duration
|
||||||
)]
|
)]
|
||||||
pub start_timeout_ms: Duration,
|
pub start_timeout_ms: Duration,
|
||||||
@@ -340,7 +465,7 @@ pub struct GenesisConfiguration {
|
|||||||
impl GenesisConfiguration {
|
impl GenesisConfiguration {
|
||||||
pub fn genesis(&self) -> anyhow::Result<&Genesis> {
|
pub fn genesis(&self) -> anyhow::Result<&Genesis> {
|
||||||
static DEFAULT_GENESIS: LazyLock<Genesis> = LazyLock::new(|| {
|
static DEFAULT_GENESIS: LazyLock<Genesis> = LazyLock::new(|| {
|
||||||
let genesis = include_str!("../../../genesis.json");
|
let genesis = include_str!("../../../assets/dev-genesis.json");
|
||||||
serde_json::from_str(genesis).unwrap()
|
serde_json::from_str(genesis).unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -374,7 +499,7 @@ pub struct WalletConfiguration {
|
|||||||
/// This argument controls which private keys the nodes should have access to and be added to
|
/// This argument controls which private keys the nodes should have access to and be added to
|
||||||
/// its wallet signers. With a value of N, private keys (0, N] will be added to the signer set
|
/// its wallet signers. With a value of N, private keys (0, N] will be added to the signer set
|
||||||
/// of the node.
|
/// of the node.
|
||||||
#[clap(long = "wallet.additional-keys", default_value_t = 100_000)]
|
#[clap(long = "wallet.additional-keys", default_value_t = 200)]
|
||||||
additional_keys: usize,
|
additional_keys: usize,
|
||||||
|
|
||||||
/// The wallet object that will be used.
|
/// The wallet object that will be used.
|
||||||
@@ -399,6 +524,10 @@ impl WalletConfiguration {
|
|||||||
})
|
})
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn highest_private_key_exclusive(&self) -> U256 {
|
||||||
|
U256::try_from(self.additional_keys).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_private_key<S>(value: &PrivateKeySigner, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize_private_key<S>(value: &PrivateKeySigner, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use revive_dt_common::iterators::FilesWithExtensionIterator;
|
use revive_dt_common::{iterators::FilesWithExtensionIterator, types::CompilerIdentifier};
|
||||||
use revive_dt_compiler::{Compiler, CompilerOutput, Mode, SolidityCompiler};
|
use revive_dt_compiler::{Compiler, CompilerOutput, Mode, SolidityCompiler};
|
||||||
use revive_dt_config::TestingPlatform;
|
use revive_dt_core::Platform;
|
||||||
use revive_dt_format::metadata::{ContractIdent, ContractInstance, Metadata};
|
use revive_dt_format::metadata::{ContractIdent, ContractInstance, Metadata};
|
||||||
|
|
||||||
use alloy::{hex::ToHexExt, json_abi::JsonAbi, primitives::Address};
|
use alloy::{hex::ToHexExt, json_abi::JsonAbi, primitives::Address};
|
||||||
@@ -22,8 +22,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
use tokio::sync::{Mutex, RwLock};
|
use tokio::sync::{Mutex, RwLock};
|
||||||
use tracing::{Instrument, debug, debug_span, instrument};
|
use tracing::{Instrument, debug, debug_span, instrument};
|
||||||
|
|
||||||
use crate::Platform;
|
|
||||||
|
|
||||||
pub struct CachedCompiler<'a> {
|
pub struct CachedCompiler<'a> {
|
||||||
/// The cache that stores the compiled contracts.
|
/// The cache that stores the compiled contracts.
|
||||||
artifacts_cache: ArtifactsCache,
|
artifacts_cache: ArtifactsCache,
|
||||||
@@ -57,21 +55,22 @@ impl<'a> CachedCompiler<'a> {
|
|||||||
fields(
|
fields(
|
||||||
metadata_file_path = %metadata_file_path.display(),
|
metadata_file_path = %metadata_file_path.display(),
|
||||||
%mode,
|
%mode,
|
||||||
platform = P::config_id().to_string()
|
platform = %platform.platform_identifier()
|
||||||
),
|
),
|
||||||
err
|
err
|
||||||
)]
|
)]
|
||||||
pub async fn compile_contracts<P: Platform>(
|
pub async fn compile_contracts(
|
||||||
&self,
|
&self,
|
||||||
metadata: &'a Metadata,
|
metadata: &'a Metadata,
|
||||||
metadata_file_path: &'a Path,
|
metadata_file_path: &'a Path,
|
||||||
mode: Cow<'a, Mode>,
|
mode: Cow<'a, Mode>,
|
||||||
deployed_libraries: Option<&HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
|
deployed_libraries: Option<&HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
|
||||||
compiler: &P::Compiler,
|
compiler: &dyn SolidityCompiler,
|
||||||
|
platform: &dyn Platform,
|
||||||
reporter: &ExecutionSpecificReporter,
|
reporter: &ExecutionSpecificReporter,
|
||||||
) -> Result<CompilerOutput> {
|
) -> Result<CompilerOutput> {
|
||||||
let cache_key = CacheKey {
|
let cache_key = CacheKey {
|
||||||
platform_key: P::config_id(),
|
compiler_identifier: platform.compiler_identifier(),
|
||||||
compiler_version: compiler.version().clone(),
|
compiler_version: compiler.version().clone(),
|
||||||
metadata_file_path,
|
metadata_file_path,
|
||||||
solc_mode: mode.clone(),
|
solc_mode: mode.clone(),
|
||||||
@@ -79,7 +78,7 @@ impl<'a> CachedCompiler<'a> {
|
|||||||
|
|
||||||
let compilation_callback = || {
|
let compilation_callback = || {
|
||||||
async move {
|
async move {
|
||||||
compile_contracts::<P>(
|
compile_contracts(
|
||||||
metadata
|
metadata
|
||||||
.directory()
|
.directory()
|
||||||
.context("Failed to get metadata directory while preparing compilation")?,
|
.context("Failed to get metadata directory while preparing compilation")?,
|
||||||
@@ -96,7 +95,7 @@ impl<'a> CachedCompiler<'a> {
|
|||||||
}
|
}
|
||||||
.instrument(debug_span!(
|
.instrument(debug_span!(
|
||||||
"Running compilation for the cache key",
|
"Running compilation for the cache key",
|
||||||
cache_key.platform_key = %cache_key.platform_key,
|
cache_key.compiler_identifier = %cache_key.compiler_identifier,
|
||||||
cache_key.compiler_version = %cache_key.compiler_version,
|
cache_key.compiler_version = %cache_key.compiler_version,
|
||||||
cache_key.metadata_file_path = %cache_key.metadata_file_path.display(),
|
cache_key.metadata_file_path = %cache_key.metadata_file_path.display(),
|
||||||
cache_key.solc_mode = %cache_key.solc_mode,
|
cache_key.solc_mode = %cache_key.solc_mode,
|
||||||
@@ -179,12 +178,12 @@ impl<'a> CachedCompiler<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn compile_contracts<P: Platform>(
|
async fn compile_contracts(
|
||||||
metadata_directory: impl AsRef<Path>,
|
metadata_directory: impl AsRef<Path>,
|
||||||
mut files_to_compile: impl Iterator<Item = PathBuf>,
|
mut files_to_compile: impl Iterator<Item = PathBuf>,
|
||||||
mode: &Mode,
|
mode: &Mode,
|
||||||
deployed_libraries: Option<&HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
|
deployed_libraries: Option<&HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
|
||||||
compiler: &P::Compiler,
|
compiler: &dyn SolidityCompiler,
|
||||||
reporter: &ExecutionSpecificReporter,
|
reporter: &ExecutionSpecificReporter,
|
||||||
) -> Result<CompilerOutput> {
|
) -> Result<CompilerOutput> {
|
||||||
let all_sources_in_dir = FilesWithExtensionIterator::new(metadata_directory.as_ref())
|
let all_sources_in_dir = FilesWithExtensionIterator::new(metadata_directory.as_ref())
|
||||||
@@ -332,9 +331,8 @@ impl ArtifactsCache {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
|
||||||
struct CacheKey<'a> {
|
struct CacheKey<'a> {
|
||||||
/// The platform name that this artifact was compiled for. For example, this could be EVM or
|
/// The identifier of the used compiler.
|
||||||
/// PVM.
|
compiler_identifier: CompilerIdentifier,
|
||||||
platform_key: &'a TestingPlatform,
|
|
||||||
|
|
||||||
/// The version of the compiler that was used to compile the artifacts.
|
/// The version of the compiler that was used to compile the artifacts.
|
||||||
compiler_version: Version,
|
compiler_version: Version,
|
||||||
|
|||||||
+204
-160
@@ -1,14 +1,14 @@
|
|||||||
//! The test driver handles the compilation and execution of the test cases.
|
//! The test driver handles the compilation and execution of the test cases.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use alloy::consensus::EMPTY_ROOT_HASH;
|
use alloy::consensus::EMPTY_ROOT_HASH;
|
||||||
use alloy::hex;
|
use alloy::hex;
|
||||||
use alloy::json_abi::JsonAbi;
|
use alloy::json_abi::JsonAbi;
|
||||||
use alloy::network::{Ethereum, TransactionBuilder};
|
use alloy::network::{Ethereum, TransactionBuilder};
|
||||||
use alloy::primitives::U256;
|
use alloy::primitives::{TxHash, U256};
|
||||||
use alloy::rpc::types::TransactionReceipt;
|
use alloy::rpc::types::TransactionReceipt;
|
||||||
use alloy::rpc::types::trace::geth::{
|
use alloy::rpc::types::trace::geth::{
|
||||||
CallFrame, GethDebugBuiltInTracerType, GethDebugTracerConfig, GethDebugTracerType,
|
CallFrame, GethDebugBuiltInTracerType, GethDebugTracerConfig, GethDebugTracerType,
|
||||||
@@ -18,27 +18,28 @@ use alloy::{
|
|||||||
primitives::Address,
|
primitives::Address,
|
||||||
rpc::types::{TransactionRequest, trace::geth::DiffMode},
|
rpc::types::{TransactionRequest, trace::geth::DiffMode},
|
||||||
};
|
};
|
||||||
use anyhow::Context as _;
|
use anyhow::{Context as _, bail};
|
||||||
use futures::TryStreamExt;
|
use futures::{TryStreamExt, future::try_join_all};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
use revive_dt_common::types::{PlatformIdentifier, PrivateKeyAllocator};
|
||||||
use revive_dt_format::traits::{ResolutionContext, ResolverApi};
|
use revive_dt_format::traits::{ResolutionContext, ResolverApi};
|
||||||
use revive_dt_report::ExecutionSpecificReporter;
|
use revive_dt_report::ExecutionSpecificReporter;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
use revive_dt_format::case::Case;
|
use revive_dt_format::case::Case;
|
||||||
use revive_dt_format::input::{
|
|
||||||
BalanceAssertion, Calldata, EtherValue, Expected, ExpectedOutput, Input, Method, StepIdx,
|
|
||||||
StorageEmptyAssertion,
|
|
||||||
};
|
|
||||||
use revive_dt_format::metadata::{ContractIdent, ContractInstance, ContractPathAndIdent};
|
use revive_dt_format::metadata::{ContractIdent, ContractInstance, ContractPathAndIdent};
|
||||||
use revive_dt_format::{input::Step, metadata::Metadata};
|
use revive_dt_format::steps::{
|
||||||
|
BalanceAssertionStep, Calldata, EtherValue, Expected, ExpectedOutput, FunctionCallStep, Method,
|
||||||
|
StepIdx, StepPath, StorageEmptyAssertionStep,
|
||||||
|
};
|
||||||
|
use revive_dt_format::{metadata::Metadata, steps::Step};
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
use tokio::try_join;
|
use tokio::try_join;
|
||||||
use tracing::{Instrument, info, info_span, instrument};
|
use tracing::{Instrument, info, info_span, instrument};
|
||||||
|
|
||||||
use crate::Platform;
|
#[derive(Clone)]
|
||||||
|
pub struct CaseState {
|
||||||
pub struct CaseState<T: Platform> {
|
|
||||||
/// A map of all of the compiled contracts for the given metadata file.
|
/// A map of all of the compiled contracts for the given metadata file.
|
||||||
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
||||||
|
|
||||||
@@ -55,18 +56,18 @@ pub struct CaseState<T: Platform> {
|
|||||||
/// The execution reporter.
|
/// The execution reporter.
|
||||||
execution_reporter: ExecutionSpecificReporter,
|
execution_reporter: ExecutionSpecificReporter,
|
||||||
|
|
||||||
phantom: PhantomData<T>,
|
/// The private key allocator used for this case state. This is an Arc Mutex to allow for the
|
||||||
|
/// state to be cloned and for all of the clones to refer to the same allocator.
|
||||||
|
private_key_allocator: Arc<Mutex<PrivateKeyAllocator>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> CaseState<T>
|
impl CaseState {
|
||||||
where
|
|
||||||
T: Platform,
|
|
||||||
{
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
compiler_version: Version,
|
compiler_version: Version,
|
||||||
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
||||||
deployed_contracts: HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>,
|
deployed_contracts: HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>,
|
||||||
execution_reporter: ExecutionSpecificReporter,
|
execution_reporter: ExecutionSpecificReporter,
|
||||||
|
private_key_allocator: Arc<Mutex<PrivateKeyAllocator>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
compiled_contracts,
|
compiled_contracts,
|
||||||
@@ -74,7 +75,7 @@ where
|
|||||||
variables: Default::default(),
|
variables: Default::default(),
|
||||||
compiler_version,
|
compiler_version,
|
||||||
execution_reporter,
|
execution_reporter,
|
||||||
phantom: PhantomData,
|
private_key_allocator,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +83,8 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
step: &Step,
|
step: &Step,
|
||||||
node: &T::Blockchain,
|
step_path: &StepPath,
|
||||||
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<StepOutput> {
|
) -> anyhow::Result<StepOutput> {
|
||||||
match step {
|
match step {
|
||||||
Step::FunctionCall(input) => {
|
Step::FunctionCall(input) => {
|
||||||
@@ -104,6 +106,24 @@ where
|
|||||||
.context("Failed to handle storage empty assertion step")?;
|
.context("Failed to handle storage empty assertion step")?;
|
||||||
Ok(StepOutput::StorageEmptyAssertion)
|
Ok(StepOutput::StorageEmptyAssertion)
|
||||||
}
|
}
|
||||||
|
Step::Repeat(repetition_step) => {
|
||||||
|
self.handle_repeat(
|
||||||
|
metadata,
|
||||||
|
repetition_step.repeat,
|
||||||
|
&repetition_step.steps,
|
||||||
|
step_path,
|
||||||
|
node,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("Failed to handle the repetition step")?;
|
||||||
|
Ok(StepOutput::Repetition)
|
||||||
|
}
|
||||||
|
Step::AllocateAccount(account_allocation) => {
|
||||||
|
self.handle_account_allocation(account_allocation.variable_name.as_str())
|
||||||
|
.await
|
||||||
|
.context("Failed to allocate account")?;
|
||||||
|
Ok(StepOutput::AccountAllocation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.inspect(|_| info!("Step Succeeded"))
|
.inspect(|_| info!("Step Succeeded"))
|
||||||
}
|
}
|
||||||
@@ -112,9 +132,11 @@ where
|
|||||||
pub async fn handle_input(
|
pub async fn handle_input(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
input: &Input,
|
input: &FunctionCallStep,
|
||||||
node: &T::Blockchain,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
||||||
|
let resolver = node.resolver().await?;
|
||||||
|
|
||||||
let deployment_receipts = self
|
let deployment_receipts = self
|
||||||
.handle_input_contract_deployment(metadata, input, node)
|
.handle_input_contract_deployment(metadata, input, node)
|
||||||
.await
|
.await
|
||||||
@@ -124,14 +146,19 @@ where
|
|||||||
.await
|
.await
|
||||||
.context("Failed during transaction execution phase of input handling")?;
|
.context("Failed during transaction execution phase of input handling")?;
|
||||||
let tracing_result = self
|
let tracing_result = self
|
||||||
.handle_input_call_frame_tracing(&execution_receipt, node)
|
.handle_input_call_frame_tracing(execution_receipt.transaction_hash, node)
|
||||||
.await
|
.await
|
||||||
.context("Failed during callframe tracing phase of input handling")?;
|
.context("Failed during callframe tracing phase of input handling")?;
|
||||||
self.handle_input_variable_assignment(input, &tracing_result)
|
self.handle_input_variable_assignment(input, &tracing_result)
|
||||||
.context("Failed to assign variables from callframe output")?;
|
.context("Failed to assign variables from callframe output")?;
|
||||||
let (_, (geth_trace, diff_mode)) = try_join!(
|
let (_, (geth_trace, diff_mode)) = try_join!(
|
||||||
self.handle_input_expectations(input, &execution_receipt, node, &tracing_result),
|
self.handle_input_expectations(
|
||||||
self.handle_input_diff(&execution_receipt, node)
|
input,
|
||||||
|
&execution_receipt,
|
||||||
|
resolver.as_ref(),
|
||||||
|
&tracing_result
|
||||||
|
),
|
||||||
|
self.handle_input_diff(execution_receipt.transaction_hash, node)
|
||||||
)
|
)
|
||||||
.context("Failed while evaluating expectations and diffs in parallel")?;
|
.context("Failed while evaluating expectations and diffs in parallel")?;
|
||||||
Ok((execution_receipt, geth_trace, diff_mode))
|
Ok((execution_receipt, geth_trace, diff_mode))
|
||||||
@@ -141,8 +168,8 @@ where
|
|||||||
pub async fn handle_balance_assertion(
|
pub async fn handle_balance_assertion(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
balance_assertion: &BalanceAssertion,
|
balance_assertion: &BalanceAssertionStep,
|
||||||
node: &T::Blockchain,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
self.handle_balance_assertion_contract_deployment(metadata, balance_assertion, node)
|
self.handle_balance_assertion_contract_deployment(metadata, balance_assertion, node)
|
||||||
.await
|
.await
|
||||||
@@ -157,8 +184,8 @@ where
|
|||||||
pub async fn handle_storage_empty(
|
pub async fn handle_storage_empty(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
storage_empty: &StorageEmptyAssertion,
|
storage_empty: &StorageEmptyAssertionStep,
|
||||||
node: &T::Blockchain,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
self.handle_storage_empty_assertion_contract_deployment(metadata, storage_empty, node)
|
self.handle_storage_empty_assertion_contract_deployment(metadata, storage_empty, node)
|
||||||
.await
|
.await
|
||||||
@@ -169,13 +196,51 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", name = "Handling Repetition", skip_all)]
|
||||||
|
pub async fn handle_repeat(
|
||||||
|
&mut self,
|
||||||
|
metadata: &Metadata,
|
||||||
|
repetitions: usize,
|
||||||
|
steps: &[Step],
|
||||||
|
step_path: &StepPath,
|
||||||
|
node: &dyn EthereumNode,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let tasks = (0..repetitions).map(|_| {
|
||||||
|
let mut state = self.clone();
|
||||||
|
async move {
|
||||||
|
for (step_idx, step) in steps.iter().enumerate() {
|
||||||
|
let step_path = step_path.append(step_idx);
|
||||||
|
state.handle_step(metadata, step, &step_path, node).await?;
|
||||||
|
}
|
||||||
|
Ok::<(), anyhow::Error>(())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try_join_all(tasks).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", name = "Handling Account Allocation", skip_all)]
|
||||||
|
pub async fn handle_account_allocation(&mut self, variable_name: &str) -> anyhow::Result<()> {
|
||||||
|
let Some(variable_name) = variable_name.strip_prefix("$VARIABLE:") else {
|
||||||
|
bail!("Account allocation must start with $VARIABLE:");
|
||||||
|
};
|
||||||
|
|
||||||
|
let private_key = self.private_key_allocator.lock().await.allocate()?;
|
||||||
|
let account = private_key.address();
|
||||||
|
let variable = U256::from_be_slice(account.0.as_slice());
|
||||||
|
|
||||||
|
self.variables.insert(variable_name.to_string(), variable);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Handles the contract deployment for a given input performing it if it needs to be performed.
|
/// Handles the contract deployment for a given input performing it if it needs to be performed.
|
||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_contract_deployment(
|
async fn handle_input_contract_deployment(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
input: &Input,
|
input: &FunctionCallStep,
|
||||||
node: &T::Blockchain,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<HashMap<ContractInstance, TransactionReceipt>> {
|
) -> anyhow::Result<HashMap<ContractInstance, TransactionReceipt>> {
|
||||||
let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new();
|
let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new();
|
||||||
for instance in input.find_all_contract_instances().into_iter() {
|
for instance in input.find_all_contract_instances().into_iter() {
|
||||||
@@ -195,15 +260,16 @@ where
|
|||||||
.then_some(input.value)
|
.then_some(input.value)
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
|
let caller = {
|
||||||
|
let context = self.default_resolution_context();
|
||||||
|
let resolver = node.resolver().await?;
|
||||||
|
input
|
||||||
|
.caller
|
||||||
|
.resolve_address(resolver.as_ref(), context)
|
||||||
|
.await?
|
||||||
|
};
|
||||||
if let (_, _, Some(receipt)) = self
|
if let (_, _, Some(receipt)) = self
|
||||||
.get_or_deploy_contract_instance(
|
.get_or_deploy_contract_instance(&instance, metadata, caller, calldata, value, node)
|
||||||
&instance,
|
|
||||||
metadata,
|
|
||||||
input.caller,
|
|
||||||
calldata,
|
|
||||||
value,
|
|
||||||
node,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.context("Failed to get or deploy contract instance during input execution")?
|
.context("Failed to get or deploy contract instance during input execution")?
|
||||||
{
|
{
|
||||||
@@ -218,9 +284,9 @@ where
|
|||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_execution(
|
async fn handle_input_execution(
|
||||||
&mut self,
|
&mut self,
|
||||||
input: &Input,
|
input: &FunctionCallStep,
|
||||||
mut deployment_receipts: HashMap<ContractInstance, TransactionReceipt>,
|
mut deployment_receipts: HashMap<ContractInstance, TransactionReceipt>,
|
||||||
node: &T::Blockchain,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<TransactionReceipt> {
|
) -> anyhow::Result<TransactionReceipt> {
|
||||||
match input.method {
|
match input.method {
|
||||||
// This input was already executed when `handle_input` was called. We just need to
|
// This input was already executed when `handle_input` was called. We just need to
|
||||||
@@ -229,8 +295,9 @@ where
|
|||||||
.remove(&input.instance)
|
.remove(&input.instance)
|
||||||
.context("Failed to find deployment receipt for constructor call"),
|
.context("Failed to find deployment receipt for constructor call"),
|
||||||
Method::Fallback | Method::FunctionName(_) => {
|
Method::Fallback | Method::FunctionName(_) => {
|
||||||
|
let resolver = node.resolver().await?;
|
||||||
let tx = match input
|
let tx = match input
|
||||||
.legacy_transaction(node, self.default_resolution_context())
|
.legacy_transaction(resolver.as_ref(), self.default_resolution_context())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(tx) => tx,
|
Ok(tx) => tx,
|
||||||
@@ -250,11 +317,11 @@ where
|
|||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_call_frame_tracing(
|
async fn handle_input_call_frame_tracing(
|
||||||
&self,
|
&self,
|
||||||
execution_receipt: &TransactionReceipt,
|
tx_hash: TxHash,
|
||||||
node: &T::Blockchain,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<CallFrame> {
|
) -> anyhow::Result<CallFrame> {
|
||||||
node.trace_transaction(
|
node.trace_transaction(
|
||||||
execution_receipt,
|
tx_hash,
|
||||||
GethDebugTracingOptions {
|
GethDebugTracingOptions {
|
||||||
tracer: Some(GethDebugTracerType::BuiltInTracer(
|
tracer: Some(GethDebugTracerType::BuiltInTracer(
|
||||||
GethDebugBuiltInTracerType::CallTracer,
|
GethDebugBuiltInTracerType::CallTracer,
|
||||||
@@ -281,7 +348,7 @@ where
|
|||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
fn handle_input_variable_assignment(
|
fn handle_input_variable_assignment(
|
||||||
&mut self,
|
&mut self,
|
||||||
input: &Input,
|
input: &FunctionCallStep,
|
||||||
tracing_result: &CallFrame,
|
tracing_result: &CallFrame,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let Some(ref assignments) = input.variable_assignments else {
|
let Some(ref assignments) = input.variable_assignments else {
|
||||||
@@ -312,26 +379,26 @@ where
|
|||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_expectations(
|
async fn handle_input_expectations(
|
||||||
&self,
|
&self,
|
||||||
input: &Input,
|
input: &FunctionCallStep,
|
||||||
execution_receipt: &TransactionReceipt,
|
execution_receipt: &TransactionReceipt,
|
||||||
resolver: &impl ResolverApi,
|
resolver: &(impl ResolverApi + ?Sized),
|
||||||
tracing_result: &CallFrame,
|
tracing_result: &CallFrame,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
// Resolving the `input.expected` into a series of expectations that we can then assert on.
|
// Resolving the `input.expected` into a series of expectations that we can then assert on.
|
||||||
let mut expectations = match input {
|
let mut expectations = match input {
|
||||||
Input {
|
FunctionCallStep {
|
||||||
expected: Some(Expected::Calldata(calldata)),
|
expected: Some(Expected::Calldata(calldata)),
|
||||||
..
|
..
|
||||||
} => vec![ExpectedOutput::new().with_calldata(calldata.clone())],
|
} => vec![ExpectedOutput::new().with_calldata(calldata.clone())],
|
||||||
Input {
|
FunctionCallStep {
|
||||||
expected: Some(Expected::Expected(expected)),
|
expected: Some(Expected::Expected(expected)),
|
||||||
..
|
..
|
||||||
} => vec![expected.clone()],
|
} => vec![expected.clone()],
|
||||||
Input {
|
FunctionCallStep {
|
||||||
expected: Some(Expected::ExpectedMany(expected)),
|
expected: Some(Expected::ExpectedMany(expected)),
|
||||||
..
|
..
|
||||||
} => expected.clone(),
|
} => expected.clone(),
|
||||||
Input { expected: None, .. } => vec![ExpectedOutput::new().with_success()],
|
FunctionCallStep { expected: None, .. } => vec![ExpectedOutput::new().with_success()],
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is a bit of a special case and we have to support it separately on it's own. If it's
|
// This is a bit of a special case and we have to support it separately on it's own. If it's
|
||||||
@@ -362,7 +429,7 @@ where
|
|||||||
async fn handle_input_expectation_item(
|
async fn handle_input_expectation_item(
|
||||||
&self,
|
&self,
|
||||||
execution_receipt: &TransactionReceipt,
|
execution_receipt: &TransactionReceipt,
|
||||||
resolver: &impl ResolverApi,
|
resolver: &(impl ResolverApi + ?Sized),
|
||||||
expectation: ExpectedOutput,
|
expectation: ExpectedOutput,
|
||||||
tracing_result: &CallFrame,
|
tracing_result: &CallFrame,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
@@ -432,13 +499,9 @@ where
|
|||||||
{
|
{
|
||||||
// Handling the emitter assertion.
|
// Handling the emitter assertion.
|
||||||
if let Some(ref expected_address) = expected_event.address {
|
if let Some(ref expected_address) = expected_event.address {
|
||||||
let expected = Address::from_slice(
|
let expected = expected_address
|
||||||
Calldata::new_compound([expected_address])
|
.resolve_address(resolver, resolution_context)
|
||||||
.calldata(resolver, resolution_context)
|
.await?;
|
||||||
.await?
|
|
||||||
.get(12..32)
|
|
||||||
.expect("Can't fail"),
|
|
||||||
);
|
|
||||||
let actual = actual_event.address();
|
let actual = actual_event.address();
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
@@ -507,8 +570,8 @@ where
|
|||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_diff(
|
async fn handle_input_diff(
|
||||||
&self,
|
&self,
|
||||||
execution_receipt: &TransactionReceipt,
|
tx_hash: TxHash,
|
||||||
node: &T::Blockchain,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<(GethTrace, DiffMode)> {
|
) -> anyhow::Result<(GethTrace, DiffMode)> {
|
||||||
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
||||||
diff_mode: Some(true),
|
diff_mode: Some(true),
|
||||||
@@ -517,11 +580,11 @@ where
|
|||||||
});
|
});
|
||||||
|
|
||||||
let trace = node
|
let trace = node
|
||||||
.trace_transaction(execution_receipt, trace_options)
|
.trace_transaction(tx_hash, trace_options)
|
||||||
.await
|
.await
|
||||||
.context("Failed to obtain geth prestate tracer output")?;
|
.context("Failed to obtain geth prestate tracer output")?;
|
||||||
let diff = node
|
let diff = node
|
||||||
.state_diff(execution_receipt)
|
.state_diff(tx_hash)
|
||||||
.await
|
.await
|
||||||
.context("Failed to obtain state diff for transaction")?;
|
.context("Failed to obtain state diff for transaction")?;
|
||||||
|
|
||||||
@@ -532,20 +595,20 @@ where
|
|||||||
pub async fn handle_balance_assertion_contract_deployment(
|
pub async fn handle_balance_assertion_contract_deployment(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
balance_assertion: &BalanceAssertion,
|
balance_assertion: &BalanceAssertionStep,
|
||||||
node: &T::Blockchain,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let Some(instance) = balance_assertion
|
let Some(address) = balance_assertion.address.as_resolvable_address() else {
|
||||||
.address
|
|
||||||
.strip_suffix(".address")
|
|
||||||
.map(ContractInstance::new)
|
|
||||||
else {
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
let Some(instance) = address.strip_suffix(".address").map(ContractInstance::new) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
self.get_or_deploy_contract_instance(
|
self.get_or_deploy_contract_instance(
|
||||||
&instance,
|
&instance,
|
||||||
metadata,
|
metadata,
|
||||||
Input::default_caller(),
|
FunctionCallStep::default_caller_address(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
node,
|
node,
|
||||||
@@ -557,20 +620,17 @@ where
|
|||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
pub async fn handle_balance_assertion_execution(
|
pub async fn handle_balance_assertion_execution(
|
||||||
&mut self,
|
&mut self,
|
||||||
BalanceAssertion {
|
BalanceAssertionStep {
|
||||||
address: address_string,
|
address,
|
||||||
expected_balance: amount,
|
expected_balance: amount,
|
||||||
..
|
..
|
||||||
}: &BalanceAssertion,
|
}: &BalanceAssertionStep,
|
||||||
node: &T::Blockchain,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let address = Address::from_slice(
|
let resolver = node.resolver().await?;
|
||||||
Calldata::new_compound([address_string])
|
let address = address
|
||||||
.calldata(node, self.default_resolution_context())
|
.resolve_address(resolver.as_ref(), self.default_resolution_context())
|
||||||
.await?
|
.await?;
|
||||||
.get(12..32)
|
|
||||||
.expect("Can't fail"),
|
|
||||||
);
|
|
||||||
|
|
||||||
let balance = node.balance_of(address).await?;
|
let balance = node.balance_of(address).await?;
|
||||||
|
|
||||||
@@ -582,7 +642,7 @@ where
|
|||||||
"Balance assertion failed - Expected {} but got {} for {} resolved to {}",
|
"Balance assertion failed - Expected {} but got {} for {} resolved to {}",
|
||||||
expected,
|
expected,
|
||||||
actual,
|
actual,
|
||||||
address_string,
|
address,
|
||||||
address,
|
address,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -594,20 +654,20 @@ where
|
|||||||
pub async fn handle_storage_empty_assertion_contract_deployment(
|
pub async fn handle_storage_empty_assertion_contract_deployment(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
storage_empty_assertion: &StorageEmptyAssertion,
|
storage_empty_assertion: &StorageEmptyAssertionStep,
|
||||||
node: &T::Blockchain,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let Some(instance) = storage_empty_assertion
|
let Some(address) = storage_empty_assertion.address.as_resolvable_address() else {
|
||||||
.address
|
|
||||||
.strip_suffix(".address")
|
|
||||||
.map(ContractInstance::new)
|
|
||||||
else {
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
let Some(instance) = address.strip_suffix(".address").map(ContractInstance::new) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
self.get_or_deploy_contract_instance(
|
self.get_or_deploy_contract_instance(
|
||||||
&instance,
|
&instance,
|
||||||
metadata,
|
metadata,
|
||||||
Input::default_caller(),
|
FunctionCallStep::default_caller_address(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
node,
|
node,
|
||||||
@@ -619,20 +679,17 @@ where
|
|||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
pub async fn handle_storage_empty_assertion_execution(
|
pub async fn handle_storage_empty_assertion_execution(
|
||||||
&mut self,
|
&mut self,
|
||||||
StorageEmptyAssertion {
|
StorageEmptyAssertionStep {
|
||||||
address: address_string,
|
address,
|
||||||
is_storage_empty,
|
is_storage_empty,
|
||||||
..
|
..
|
||||||
}: &StorageEmptyAssertion,
|
}: &StorageEmptyAssertionStep,
|
||||||
node: &T::Blockchain,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let address = Address::from_slice(
|
let resolver = node.resolver().await?;
|
||||||
Calldata::new_compound([address_string])
|
let address = address
|
||||||
.calldata(node, self.default_resolution_context())
|
.resolve_address(resolver.as_ref(), self.default_resolution_context())
|
||||||
.await?
|
.await?;
|
||||||
.get(12..32)
|
|
||||||
.expect("Can't fail"),
|
|
||||||
);
|
|
||||||
|
|
||||||
let storage = node.latest_state_proof(address, Default::default()).await?;
|
let storage = node.latest_state_proof(address, Default::default()).await?;
|
||||||
let is_empty = storage.storage_hash == EMPTY_ROOT_HASH;
|
let is_empty = storage.storage_hash == EMPTY_ROOT_HASH;
|
||||||
@@ -646,7 +703,7 @@ where
|
|||||||
"Storage Empty Assertion failed - Expected {} but got {} for {} resolved to {}",
|
"Storage Empty Assertion failed - Expected {} but got {} for {} resolved to {}",
|
||||||
expected,
|
expected,
|
||||||
actual,
|
actual,
|
||||||
address_string,
|
address,
|
||||||
address,
|
address,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@@ -667,7 +724,7 @@ where
|
|||||||
deployer: Address,
|
deployer: Address,
|
||||||
calldata: Option<&Calldata>,
|
calldata: Option<&Calldata>,
|
||||||
value: Option<EtherValue>,
|
value: Option<EtherValue>,
|
||||||
node: &T::Blockchain,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<(Address, JsonAbi, Option<TransactionReceipt>)> {
|
) -> anyhow::Result<(Address, JsonAbi, Option<TransactionReceipt>)> {
|
||||||
if let Some((_, address, abi)) = self.deployed_contracts.get(contract_instance) {
|
if let Some((_, address, abi)) = self.deployed_contracts.get(contract_instance) {
|
||||||
return Ok((*address, abi.clone(), None));
|
return Ok((*address, abi.clone(), None));
|
||||||
@@ -710,8 +767,9 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(calldata) = calldata {
|
if let Some(calldata) = calldata {
|
||||||
|
let resolver = node.resolver().await?;
|
||||||
let calldata = calldata
|
let calldata = calldata
|
||||||
.calldata(node, self.default_resolution_context())
|
.calldata(resolver.as_ref(), self.default_resolution_context())
|
||||||
.await?;
|
.await?;
|
||||||
code.extend(calldata);
|
code.extend(calldata);
|
||||||
}
|
}
|
||||||
@@ -728,11 +786,7 @@ where
|
|||||||
let receipt = match node.execute_transaction(tx).await {
|
let receipt = match node.execute_transaction(tx).await {
|
||||||
Ok(receipt) => receipt,
|
Ok(receipt) => receipt,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::error!(
|
tracing::error!(?error, "Contract deployment transaction failed.");
|
||||||
node = std::any::type_name::<T>(),
|
|
||||||
?error,
|
|
||||||
"Contract deployment transaction failed."
|
|
||||||
);
|
|
||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -763,36 +817,23 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CaseDriver<'a, Leader: Platform, Follower: Platform> {
|
pub struct CaseDriver<'a> {
|
||||||
metadata: &'a Metadata,
|
metadata: &'a Metadata,
|
||||||
case: &'a Case,
|
case: &'a Case,
|
||||||
leader_node: &'a Leader::Blockchain,
|
platform_state: Vec<(&'a dyn EthereumNode, PlatformIdentifier, CaseState)>,
|
||||||
follower_node: &'a Follower::Blockchain,
|
|
||||||
leader_state: CaseState<Leader>,
|
|
||||||
follower_state: CaseState<Follower>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, L, F> CaseDriver<'a, L, F>
|
impl<'a> CaseDriver<'a> {
|
||||||
where
|
|
||||||
L: Platform,
|
|
||||||
F: Platform,
|
|
||||||
{
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
metadata: &'a Metadata,
|
metadata: &'a Metadata,
|
||||||
case: &'a Case,
|
case: &'a Case,
|
||||||
leader_node: &'a L::Blockchain,
|
platform_state: Vec<(&'a dyn EthereumNode, PlatformIdentifier, CaseState)>,
|
||||||
follower_node: &'a F::Blockchain,
|
) -> CaseDriver<'a> {
|
||||||
leader_state: CaseState<L>,
|
|
||||||
follower_state: CaseState<F>,
|
|
||||||
) -> CaseDriver<'a, L, F> {
|
|
||||||
Self {
|
Self {
|
||||||
metadata,
|
metadata,
|
||||||
case,
|
case,
|
||||||
leader_node,
|
platform_state,
|
||||||
follower_node,
|
|
||||||
leader_state,
|
|
||||||
follower_state,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -805,43 +846,44 @@ where
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(idx, v)| (StepIdx::new(idx), v))
|
.map(|(idx, v)| (StepIdx::new(idx), v))
|
||||||
{
|
{
|
||||||
let (leader_step_output, follower_step_output) = try_join!(
|
let metadata = self.metadata;
|
||||||
self.leader_state
|
let step_futures =
|
||||||
.handle_step(self.metadata, &step, self.leader_node)
|
self.platform_state
|
||||||
.instrument(info_span!(
|
.iter_mut()
|
||||||
|
.map(|(node, platform_id, case_state)| {
|
||||||
|
let platform_id = *platform_id;
|
||||||
|
let node_ref = *node;
|
||||||
|
let step = step.clone();
|
||||||
|
let span = info_span!(
|
||||||
"Handling Step",
|
"Handling Step",
|
||||||
%step_idx,
|
%step_idx,
|
||||||
target = "Leader",
|
platform = %platform_id,
|
||||||
)),
|
);
|
||||||
self.follower_state
|
async move {
|
||||||
.handle_step(self.metadata, &step, self.follower_node)
|
let step_path = StepPath::from_iterator([step_idx]);
|
||||||
.instrument(info_span!(
|
case_state
|
||||||
"Handling Step",
|
.handle_step(metadata, &step, &step_path, node_ref)
|
||||||
%step_idx,
|
.await
|
||||||
target = "Follower",
|
.map_err(|e| (platform_id, e))
|
||||||
))
|
|
||||||
)?;
|
|
||||||
|
|
||||||
match (leader_step_output, follower_step_output) {
|
|
||||||
(StepOutput::FunctionCall(..), StepOutput::FunctionCall(..)) => {
|
|
||||||
// TODO: We need to actually work out how/if we will compare the diff between
|
|
||||||
// the leader and the follower. The diffs are almost guaranteed to be different
|
|
||||||
// from leader and follower and therefore without an actual strategy for this
|
|
||||||
// we have something that's guaranteed to fail. Even a simple call to some
|
|
||||||
// contract will produce two non-equal diffs because on the leader the contract
|
|
||||||
// has address X and on the follower it has address Y. On the leader contract X
|
|
||||||
// contains address A in the state and on the follower it contains address B. So
|
|
||||||
// this isn't exactly a straightforward thing to do and I'm not even sure that
|
|
||||||
// it's possible to do. Once we have an actual strategy for doing the diffs we
|
|
||||||
// will implement it here. Until then, this remains empty.
|
|
||||||
}
|
|
||||||
(StepOutput::BalanceAssertion, StepOutput::BalanceAssertion) => {}
|
|
||||||
(StepOutput::StorageEmptyAssertion, StepOutput::StorageEmptyAssertion) => {}
|
|
||||||
_ => unreachable!("The two step outputs can not be of a different kind"),
|
|
||||||
}
|
}
|
||||||
|
.instrument(span)
|
||||||
|
});
|
||||||
|
|
||||||
|
match try_join_all(step_futures).await {
|
||||||
|
Ok(_outputs) => {
|
||||||
steps_executed += 1;
|
steps_executed += 1;
|
||||||
}
|
}
|
||||||
|
Err((platform_id, error)) => {
|
||||||
|
tracing::error!(
|
||||||
|
%step_idx,
|
||||||
|
platform = %platform_id,
|
||||||
|
?error,
|
||||||
|
"Step failed on platform",
|
||||||
|
);
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(steps_executed)
|
Ok(steps_executed)
|
||||||
}
|
}
|
||||||
@@ -853,4 +895,6 @@ pub enum StepOutput {
|
|||||||
FunctionCall(TransactionReceipt, GethTrace, DiffMode),
|
FunctionCall(TransactionReceipt, GethTrace, DiffMode),
|
||||||
BalanceAssertion,
|
BalanceAssertion,
|
||||||
StorageEmptyAssertion,
|
StorageEmptyAssertion,
|
||||||
|
Repetition,
|
||||||
|
AccountAllocation,
|
||||||
}
|
}
|
||||||
|
|||||||
+403
-25
@@ -3,45 +3,423 @@
|
|||||||
//! This crate defines the testing configuration and
|
//! This crate defines the testing configuration and
|
||||||
//! provides a helper utility to execute tests.
|
//! provides a helper utility to execute tests.
|
||||||
|
|
||||||
use revive_dt_compiler::{SolidityCompiler, revive_resolc, solc};
|
use std::{
|
||||||
use revive_dt_config::TestingPlatform;
|
pin::Pin,
|
||||||
use revive_dt_format::traits::ResolverApi;
|
thread::{self, JoinHandle},
|
||||||
use revive_dt_node::{Node, geth, kitchensink::KitchensinkNode};
|
};
|
||||||
|
|
||||||
|
use alloy::genesis::Genesis;
|
||||||
|
use anyhow::Context as _;
|
||||||
|
use revive_dt_common::types::*;
|
||||||
|
use revive_dt_compiler::{SolidityCompiler, revive_resolc::Resolc, solc::Solc};
|
||||||
|
use revive_dt_config::*;
|
||||||
|
use revive_dt_node::{
|
||||||
|
Node, geth::GethNode, lighthouse_geth::LighthouseGethNode, substrate::SubstrateNode,
|
||||||
|
};
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
pub mod driver;
|
pub mod driver;
|
||||||
|
|
||||||
/// One platform can be tested differentially against another.
|
/// A trait that describes the interface for the platforms that are supported by the tool.
|
||||||
///
|
#[allow(clippy::type_complexity)]
|
||||||
/// For this we need a blockchain node implementation and a compiler.
|
|
||||||
pub trait Platform {
|
pub trait Platform {
|
||||||
type Blockchain: EthereumNode + Node + ResolverApi;
|
/// Returns the identifier of this platform. This is a combination of the node and the compiler
|
||||||
type Compiler: SolidityCompiler;
|
/// used.
|
||||||
|
fn platform_identifier(&self) -> PlatformIdentifier;
|
||||||
|
|
||||||
/// Returns the matching [TestingPlatform] of the [revive_dt_config::Arguments].
|
/// Returns a full identifier for the platform.
|
||||||
fn config_id() -> &'static TestingPlatform;
|
fn full_identifier(&self) -> (NodeIdentifier, VmIdentifier, CompilerIdentifier) {
|
||||||
|
(
|
||||||
|
self.node_identifier(),
|
||||||
|
self.vm_identifier(),
|
||||||
|
self.compiler_identifier(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the identifier of the node used.
|
||||||
|
fn node_identifier(&self) -> NodeIdentifier;
|
||||||
|
|
||||||
|
/// Returns the identifier of the vm used.
|
||||||
|
fn vm_identifier(&self) -> VmIdentifier;
|
||||||
|
|
||||||
|
/// Returns the identifier of the compiler used.
|
||||||
|
fn compiler_identifier(&self) -> CompilerIdentifier;
|
||||||
|
|
||||||
|
/// Creates a new node for the platform by spawning a new thread, creating the node object,
|
||||||
|
/// initializing it, spawning it, and waiting for it to start up.
|
||||||
|
fn new_node(
|
||||||
|
&self,
|
||||||
|
context: Context,
|
||||||
|
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>>;
|
||||||
|
|
||||||
|
/// Creates a new compiler for the provided platform
|
||||||
|
fn new_compiler(
|
||||||
|
&self,
|
||||||
|
context: Context,
|
||||||
|
version: Option<VersionOrRequirement>,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
||||||
pub struct Geth;
|
pub struct GethEvmSolcPlatform;
|
||||||
|
|
||||||
impl Platform for Geth {
|
impl Platform for GethEvmSolcPlatform {
|
||||||
type Blockchain = geth::GethNode;
|
fn platform_identifier(&self) -> PlatformIdentifier {
|
||||||
type Compiler = solc::Solc;
|
PlatformIdentifier::GethEvmSolc
|
||||||
|
}
|
||||||
|
|
||||||
fn config_id() -> &'static TestingPlatform {
|
fn node_identifier(&self) -> NodeIdentifier {
|
||||||
&TestingPlatform::Geth
|
NodeIdentifier::Geth
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vm_identifier(&self) -> VmIdentifier {
|
||||||
|
VmIdentifier::Evm
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compiler_identifier(&self) -> CompilerIdentifier {
|
||||||
|
CompilerIdentifier::Solc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_node(
|
||||||
|
&self,
|
||||||
|
context: Context,
|
||||||
|
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
|
||||||
|
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
|
||||||
|
let genesis = genesis_configuration.genesis()?.clone();
|
||||||
|
Ok(thread::spawn(move || {
|
||||||
|
let node = GethNode::new(context);
|
||||||
|
let node = spawn_node::<GethNode>(node, genesis)?;
|
||||||
|
Ok(Box::new(node) as Box<_>)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_compiler(
|
||||||
|
&self,
|
||||||
|
context: Context,
|
||||||
|
version: Option<VersionOrRequirement>,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let compiler = Solc::new(context, version).await;
|
||||||
|
compiler.map(|compiler| Box::new(compiler) as Box<dyn SolidityCompiler>)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
||||||
pub struct Kitchensink;
|
pub struct LighthouseGethEvmSolcPlatform;
|
||||||
|
|
||||||
impl Platform for Kitchensink {
|
impl Platform for LighthouseGethEvmSolcPlatform {
|
||||||
type Blockchain = KitchensinkNode;
|
fn platform_identifier(&self) -> PlatformIdentifier {
|
||||||
type Compiler = revive_resolc::Resolc;
|
PlatformIdentifier::LighthouseGethEvmSolc
|
||||||
|
}
|
||||||
|
|
||||||
fn config_id() -> &'static TestingPlatform {
|
fn node_identifier(&self) -> NodeIdentifier {
|
||||||
&TestingPlatform::Kitchensink
|
NodeIdentifier::LighthouseGeth
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vm_identifier(&self) -> VmIdentifier {
|
||||||
|
VmIdentifier::Evm
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compiler_identifier(&self) -> CompilerIdentifier {
|
||||||
|
CompilerIdentifier::Solc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_node(
|
||||||
|
&self,
|
||||||
|
context: Context,
|
||||||
|
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
|
||||||
|
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
|
||||||
|
let genesis = genesis_configuration.genesis()?.clone();
|
||||||
|
Ok(thread::spawn(move || {
|
||||||
|
let node = LighthouseGethNode::new(context);
|
||||||
|
let node = spawn_node::<LighthouseGethNode>(node, genesis)?;
|
||||||
|
Ok(Box::new(node) as Box<_>)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_compiler(
|
||||||
|
&self,
|
||||||
|
context: Context,
|
||||||
|
version: Option<VersionOrRequirement>,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let compiler = Solc::new(context, version).await;
|
||||||
|
compiler.map(|compiler| Box::new(compiler) as Box<dyn SolidityCompiler>)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
||||||
|
pub struct KitchensinkPolkavmResolcPlatform;
|
||||||
|
|
||||||
|
impl Platform for KitchensinkPolkavmResolcPlatform {
|
||||||
|
fn platform_identifier(&self) -> PlatformIdentifier {
|
||||||
|
PlatformIdentifier::KitchensinkPolkavmResolc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_identifier(&self) -> NodeIdentifier {
|
||||||
|
NodeIdentifier::Kitchensink
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vm_identifier(&self) -> VmIdentifier {
|
||||||
|
VmIdentifier::PolkaVM
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compiler_identifier(&self) -> CompilerIdentifier {
|
||||||
|
CompilerIdentifier::Resolc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_node(
|
||||||
|
&self,
|
||||||
|
context: Context,
|
||||||
|
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
|
||||||
|
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
|
||||||
|
let kitchensink_path = AsRef::<KitchensinkConfiguration>::as_ref(&context)
|
||||||
|
.path
|
||||||
|
.clone();
|
||||||
|
let genesis = genesis_configuration.genesis()?.clone();
|
||||||
|
Ok(thread::spawn(move || {
|
||||||
|
let node = SubstrateNode::new(
|
||||||
|
kitchensink_path,
|
||||||
|
SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
let node = spawn_node(node, genesis)?;
|
||||||
|
Ok(Box::new(node) as Box<_>)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_compiler(
|
||||||
|
&self,
|
||||||
|
context: Context,
|
||||||
|
version: Option<VersionOrRequirement>,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let compiler = Resolc::new(context, version).await;
|
||||||
|
compiler.map(|compiler| Box::new(compiler) as Box<dyn SolidityCompiler>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
||||||
|
pub struct KitchensinkRevmSolcPlatform;
|
||||||
|
|
||||||
|
impl Platform for KitchensinkRevmSolcPlatform {
|
||||||
|
fn platform_identifier(&self) -> PlatformIdentifier {
|
||||||
|
PlatformIdentifier::KitchensinkRevmSolc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_identifier(&self) -> NodeIdentifier {
|
||||||
|
NodeIdentifier::Kitchensink
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vm_identifier(&self) -> VmIdentifier {
|
||||||
|
VmIdentifier::Evm
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compiler_identifier(&self) -> CompilerIdentifier {
|
||||||
|
CompilerIdentifier::Solc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_node(
|
||||||
|
&self,
|
||||||
|
context: Context,
|
||||||
|
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
|
||||||
|
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
|
||||||
|
let kitchensink_path = AsRef::<KitchensinkConfiguration>::as_ref(&context)
|
||||||
|
.path
|
||||||
|
.clone();
|
||||||
|
let genesis = genesis_configuration.genesis()?.clone();
|
||||||
|
Ok(thread::spawn(move || {
|
||||||
|
let node = SubstrateNode::new(
|
||||||
|
kitchensink_path,
|
||||||
|
SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
let node = spawn_node(node, genesis)?;
|
||||||
|
Ok(Box::new(node) as Box<_>)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_compiler(
|
||||||
|
&self,
|
||||||
|
context: Context,
|
||||||
|
version: Option<VersionOrRequirement>,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let compiler = Solc::new(context, version).await;
|
||||||
|
compiler.map(|compiler| Box::new(compiler) as Box<dyn SolidityCompiler>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
||||||
|
pub struct ReviveDevNodePolkavmResolcPlatform;
|
||||||
|
|
||||||
|
impl Platform for ReviveDevNodePolkavmResolcPlatform {
|
||||||
|
fn platform_identifier(&self) -> PlatformIdentifier {
|
||||||
|
PlatformIdentifier::ReviveDevNodePolkavmResolc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_identifier(&self) -> NodeIdentifier {
|
||||||
|
NodeIdentifier::ReviveDevNode
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vm_identifier(&self) -> VmIdentifier {
|
||||||
|
VmIdentifier::PolkaVM
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compiler_identifier(&self) -> CompilerIdentifier {
|
||||||
|
CompilerIdentifier::Resolc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_node(
|
||||||
|
&self,
|
||||||
|
context: Context,
|
||||||
|
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
|
||||||
|
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
|
||||||
|
let revive_dev_node_path = AsRef::<ReviveDevNodeConfiguration>::as_ref(&context)
|
||||||
|
.path
|
||||||
|
.clone();
|
||||||
|
let genesis = genesis_configuration.genesis()?.clone();
|
||||||
|
Ok(thread::spawn(move || {
|
||||||
|
let node = SubstrateNode::new(
|
||||||
|
revive_dev_node_path,
|
||||||
|
SubstrateNode::REVIVE_DEV_NODE_EXPORT_CHAINSPEC_COMMAND,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
let node = spawn_node(node, genesis)?;
|
||||||
|
Ok(Box::new(node) as Box<_>)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_compiler(
|
||||||
|
&self,
|
||||||
|
context: Context,
|
||||||
|
version: Option<VersionOrRequirement>,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let compiler = Resolc::new(context, version).await;
|
||||||
|
compiler.map(|compiler| Box::new(compiler) as Box<dyn SolidityCompiler>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
||||||
|
pub struct ReviveDevNodeRevmSolcPlatform;
|
||||||
|
|
||||||
|
impl Platform for ReviveDevNodeRevmSolcPlatform {
|
||||||
|
fn platform_identifier(&self) -> PlatformIdentifier {
|
||||||
|
PlatformIdentifier::ReviveDevNodeRevmSolc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_identifier(&self) -> NodeIdentifier {
|
||||||
|
NodeIdentifier::ReviveDevNode
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vm_identifier(&self) -> VmIdentifier {
|
||||||
|
VmIdentifier::Evm
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compiler_identifier(&self) -> CompilerIdentifier {
|
||||||
|
CompilerIdentifier::Solc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_node(
|
||||||
|
&self,
|
||||||
|
context: Context,
|
||||||
|
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
|
||||||
|
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
|
||||||
|
let revive_dev_node_path = AsRef::<ReviveDevNodeConfiguration>::as_ref(&context)
|
||||||
|
.path
|
||||||
|
.clone();
|
||||||
|
let genesis = genesis_configuration.genesis()?.clone();
|
||||||
|
Ok(thread::spawn(move || {
|
||||||
|
let node = SubstrateNode::new(
|
||||||
|
revive_dev_node_path,
|
||||||
|
SubstrateNode::REVIVE_DEV_NODE_EXPORT_CHAINSPEC_COMMAND,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
let node = spawn_node(node, genesis)?;
|
||||||
|
Ok(Box::new(node) as Box<_>)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_compiler(
|
||||||
|
&self,
|
||||||
|
context: Context,
|
||||||
|
version: Option<VersionOrRequirement>,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let compiler = Solc::new(context, version).await;
|
||||||
|
compiler.map(|compiler| Box::new(compiler) as Box<dyn SolidityCompiler>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PlatformIdentifier> for Box<dyn Platform> {
|
||||||
|
fn from(value: PlatformIdentifier) -> Self {
|
||||||
|
match value {
|
||||||
|
PlatformIdentifier::GethEvmSolc => Box::new(GethEvmSolcPlatform) as Box<_>,
|
||||||
|
PlatformIdentifier::LighthouseGethEvmSolc => {
|
||||||
|
Box::new(LighthouseGethEvmSolcPlatform) as Box<_>
|
||||||
|
}
|
||||||
|
PlatformIdentifier::KitchensinkPolkavmResolc => {
|
||||||
|
Box::new(KitchensinkPolkavmResolcPlatform) as Box<_>
|
||||||
|
}
|
||||||
|
PlatformIdentifier::KitchensinkRevmSolc => {
|
||||||
|
Box::new(KitchensinkRevmSolcPlatform) as Box<_>
|
||||||
|
}
|
||||||
|
PlatformIdentifier::ReviveDevNodePolkavmResolc => {
|
||||||
|
Box::new(ReviveDevNodePolkavmResolcPlatform) as Box<_>
|
||||||
|
}
|
||||||
|
PlatformIdentifier::ReviveDevNodeRevmSolc => {
|
||||||
|
Box::new(ReviveDevNodeRevmSolcPlatform) as Box<_>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PlatformIdentifier> for &dyn Platform {
|
||||||
|
fn from(value: PlatformIdentifier) -> Self {
|
||||||
|
match value {
|
||||||
|
PlatformIdentifier::GethEvmSolc => &GethEvmSolcPlatform as &dyn Platform,
|
||||||
|
PlatformIdentifier::LighthouseGethEvmSolc => {
|
||||||
|
&LighthouseGethEvmSolcPlatform as &dyn Platform
|
||||||
|
}
|
||||||
|
PlatformIdentifier::KitchensinkPolkavmResolc => {
|
||||||
|
&KitchensinkPolkavmResolcPlatform as &dyn Platform
|
||||||
|
}
|
||||||
|
PlatformIdentifier::KitchensinkRevmSolc => {
|
||||||
|
&KitchensinkRevmSolcPlatform as &dyn Platform
|
||||||
|
}
|
||||||
|
PlatformIdentifier::ReviveDevNodePolkavmResolc => {
|
||||||
|
&ReviveDevNodePolkavmResolcPlatform as &dyn Platform
|
||||||
|
}
|
||||||
|
PlatformIdentifier::ReviveDevNodeRevmSolc => {
|
||||||
|
&ReviveDevNodeRevmSolcPlatform as &dyn Platform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_node<T: Node + EthereumNode + Send + Sync>(
|
||||||
|
mut node: T,
|
||||||
|
genesis: Genesis,
|
||||||
|
) -> anyhow::Result<T> {
|
||||||
|
info!(
|
||||||
|
id = node.id(),
|
||||||
|
connection_string = node.connection_string(),
|
||||||
|
"Spawning node"
|
||||||
|
);
|
||||||
|
node.spawn(genesis)
|
||||||
|
.context("Failed to spawn node process")?;
|
||||||
|
info!(
|
||||||
|
id = node.id(),
|
||||||
|
connection_string = node.connection_string(),
|
||||||
|
"Spawned node"
|
||||||
|
);
|
||||||
|
Ok(node)
|
||||||
|
}
|
||||||
|
|||||||
+262
-298
@@ -1,8 +1,9 @@
|
|||||||
mod cached_compiler;
|
mod cached_compiler;
|
||||||
|
mod pool;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
collections::{BTreeMap, HashMap},
|
collections::{BTreeSet, HashMap},
|
||||||
io::{BufWriter, Write, stderr},
|
io::{BufWriter, Write, stderr},
|
||||||
path::Path,
|
path::Path,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
@@ -20,32 +21,35 @@ use futures::{Stream, StreamExt};
|
|||||||
use indexmap::{IndexMap, indexmap};
|
use indexmap::{IndexMap, indexmap};
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
use revive_dt_report::{
|
use revive_dt_report::{
|
||||||
NodeDesignation, ReportAggregator, Reporter, ReporterEvent, TestCaseStatus,
|
ExecutionSpecificReporter, ReportAggregator, Reporter, ReporterEvent, TestCaseStatus,
|
||||||
TestSpecificReporter, TestSpecifier,
|
TestSpecificReporter, TestSpecifier,
|
||||||
};
|
};
|
||||||
use schemars::schema_for;
|
use schemars::schema_for;
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
use tokio::try_join;
|
use tokio::sync::Mutex;
|
||||||
use tracing::{debug, error, info, info_span, instrument};
|
use tracing::{debug, error, info, info_span, instrument};
|
||||||
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
||||||
|
|
||||||
use revive_dt_common::{iterators::EitherIter, types::Mode};
|
use revive_dt_common::{
|
||||||
use revive_dt_compiler::{CompilerOutput, SolidityCompiler};
|
iterators::EitherIter,
|
||||||
|
types::{Mode, PrivateKeyAllocator},
|
||||||
|
};
|
||||||
|
use revive_dt_compiler::SolidityCompiler;
|
||||||
use revive_dt_config::{Context, *};
|
use revive_dt_config::{Context, *};
|
||||||
use revive_dt_core::{
|
use revive_dt_core::{
|
||||||
Geth, Kitchensink, Platform,
|
Platform,
|
||||||
driver::{CaseDriver, CaseState},
|
driver::{CaseDriver, CaseState},
|
||||||
};
|
};
|
||||||
use revive_dt_format::{
|
use revive_dt_format::{
|
||||||
case::{Case, CaseIdx},
|
case::{Case, CaseIdx},
|
||||||
corpus::Corpus,
|
corpus::Corpus,
|
||||||
input::{Input, Step},
|
|
||||||
metadata::{ContractPathAndIdent, Metadata, MetadataFile},
|
metadata::{ContractPathAndIdent, Metadata, MetadataFile},
|
||||||
mode::ParsedMode,
|
mode::ParsedMode,
|
||||||
|
steps::{FunctionCallStep, Step},
|
||||||
};
|
};
|
||||||
use revive_dt_node::{Node, pool::NodePool};
|
|
||||||
|
|
||||||
use crate::cached_compiler::CachedCompiler;
|
use crate::cached_compiler::CachedCompiler;
|
||||||
|
use crate::pool::NodePool;
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let (writer, _guard) = tracing_appender::non_blocking::NonBlockingBuilder::default()
|
let (writer, _guard) = tracing_appender::non_blocking::NonBlockingBuilder::default()
|
||||||
@@ -112,7 +116,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
#[instrument(level = "debug", name = "Collecting Corpora", skip_all)]
|
#[instrument(level = "debug", name = "Collecting Corpora", skip_all)]
|
||||||
fn collect_corpora(
|
fn collect_corpora(
|
||||||
context: &ExecutionContext,
|
context: &TestExecutionContext,
|
||||||
) -> anyhow::Result<HashMap<Corpus, Vec<MetadataFile>>> {
|
) -> anyhow::Result<HashMap<Corpus, Vec<MetadataFile>>> {
|
||||||
let mut corpora = HashMap::new();
|
let mut corpora = HashMap::new();
|
||||||
|
|
||||||
@@ -133,32 +137,35 @@ fn collect_corpora(
|
|||||||
Ok(corpora)
|
Ok(corpora)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_driver<L, F>(
|
async fn run_driver(
|
||||||
context: ExecutionContext,
|
context: TestExecutionContext,
|
||||||
metadata_files: &[MetadataFile],
|
metadata_files: &[MetadataFile],
|
||||||
reporter: Reporter,
|
reporter: Reporter,
|
||||||
report_aggregator_task: impl Future<Output = anyhow::Result<()>>,
|
report_aggregator_task: impl Future<Output = anyhow::Result<()>>,
|
||||||
) -> anyhow::Result<()>
|
platforms: Vec<&dyn Platform>,
|
||||||
where
|
) -> anyhow::Result<()> {
|
||||||
L: Platform,
|
let mut nodes = Vec::<(&dyn Platform, NodePool)>::new();
|
||||||
F: Platform,
|
for platform in platforms.into_iter() {
|
||||||
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
let pool = NodePool::new(Context::ExecuteTests(Box::new(context.clone())), platform)
|
||||||
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
.inspect_err(|err| {
|
||||||
{
|
error!(
|
||||||
let leader_nodes = NodePool::<L::Blockchain>::new(context.clone())
|
?err,
|
||||||
.context("Failed to initialize leader node pool")?;
|
platform_identifier = %platform.platform_identifier(),
|
||||||
let follower_nodes = NodePool::<F::Blockchain>::new(context.clone())
|
"Failed to initialize the node pool for the platform."
|
||||||
.context("Failed to initialize follower node pool")?;
|
)
|
||||||
|
})
|
||||||
|
.context("Failed to initialize the node pool")?;
|
||||||
|
nodes.push((platform, pool));
|
||||||
|
}
|
||||||
|
|
||||||
let tests_stream = tests_stream(
|
let tests_stream = tests_stream(
|
||||||
&context,
|
&context,
|
||||||
metadata_files.iter(),
|
metadata_files.iter(),
|
||||||
&leader_nodes,
|
nodes.as_slice(),
|
||||||
&follower_nodes,
|
|
||||||
reporter.clone(),
|
reporter.clone(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let driver_task = start_driver_task::<L, F>(&context, tests_stream)
|
let driver_task = start_driver_task(&context, tests_stream)
|
||||||
.await
|
.await
|
||||||
.context("Failed to start driver task")?;
|
.context("Failed to start driver task")?;
|
||||||
let cli_reporting_task = start_cli_reporting_task(reporter);
|
let cli_reporting_task = start_cli_reporting_task(reporter);
|
||||||
@@ -169,19 +176,12 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn tests_stream<'a, L, F>(
|
async fn tests_stream<'a>(
|
||||||
args: &ExecutionContext,
|
args: &TestExecutionContext,
|
||||||
metadata_files: impl IntoIterator<Item = &'a MetadataFile> + Clone,
|
metadata_files: impl IntoIterator<Item = &'a MetadataFile> + Clone,
|
||||||
leader_node_pool: &'a NodePool<L::Blockchain>,
|
nodes: &'a [(&dyn Platform, NodePool)],
|
||||||
follower_node_pool: &'a NodePool<F::Blockchain>,
|
|
||||||
reporter: Reporter,
|
reporter: Reporter,
|
||||||
) -> impl Stream<Item = Test<'a, L, F>>
|
) -> impl Stream<Item = Test<'a>> {
|
||||||
where
|
|
||||||
L: Platform,
|
|
||||||
F: Platform,
|
|
||||||
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
|
||||||
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
let tests = metadata_files
|
let tests = metadata_files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|metadata_file| {
|
.flat_map(|metadata_file| {
|
||||||
@@ -231,35 +231,36 @@ where
|
|||||||
stream::iter(tests.into_iter())
|
stream::iter(tests.into_iter())
|
||||||
.filter_map(
|
.filter_map(
|
||||||
move |(metadata_file, case_idx, case, mode, reporter)| async move {
|
move |(metadata_file, case_idx, case, mode, reporter)| async move {
|
||||||
let leader_compiler = <L::Compiler as SolidityCompiler>::new(
|
let mut platforms = Vec::new();
|
||||||
args,
|
for (platform, node_pool) in nodes.iter() {
|
||||||
|
let node = node_pool.round_robbin();
|
||||||
|
let compiler = platform
|
||||||
|
.new_compiler(
|
||||||
|
Context::ExecuteTests(Box::new(args.clone())),
|
||||||
mode.version.clone().map(Into::into),
|
mode.version.clone().map(Into::into),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.inspect_err(|err| error!(?err, "Failed to instantiate the leader compiler"))
|
.inspect_err(|err| {
|
||||||
.ok()?;
|
error!(
|
||||||
|
?err,
|
||||||
let follower_compiler = <F::Compiler as SolidityCompiler>::new(
|
platform_identifier = %platform.platform_identifier(),
|
||||||
args,
|
"Failed to instantiate the compiler"
|
||||||
mode.version.clone().map(Into::into),
|
|
||||||
)
|
)
|
||||||
.await
|
})
|
||||||
.inspect_err(|err| error!(?err, "Failed to instantiate the follower compiler"))
|
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
let leader_node = leader_node_pool.round_robbin();
|
let reporter = reporter
|
||||||
let follower_node = follower_node_pool.round_robbin();
|
.execution_specific_reporter(node.id(), platform.platform_identifier());
|
||||||
|
platforms.push((*platform, node, compiler, reporter));
|
||||||
|
}
|
||||||
|
|
||||||
Some(Test::<L, F> {
|
Some(Test {
|
||||||
metadata: metadata_file,
|
metadata: metadata_file,
|
||||||
metadata_file_path: metadata_file.metadata_file_path.as_path(),
|
metadata_file_path: metadata_file.metadata_file_path.as_path(),
|
||||||
mode: mode.clone(),
|
mode: mode.clone(),
|
||||||
case_idx: CaseIdx::new(case_idx),
|
case_idx: CaseIdx::new(case_idx),
|
||||||
case,
|
case,
|
||||||
leader_node,
|
platforms,
|
||||||
follower_node,
|
|
||||||
leader_compiler,
|
|
||||||
follower_compiler,
|
|
||||||
reporter,
|
reporter,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -293,18 +294,10 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn start_driver_task<'a, L, F>(
|
async fn start_driver_task<'a>(
|
||||||
context: &ExecutionContext,
|
context: &TestExecutionContext,
|
||||||
tests: impl Stream<Item = Test<'a, L, F>>,
|
tests: impl Stream<Item = Test<'a>>,
|
||||||
) -> anyhow::Result<impl Future<Output = ()>>
|
) -> anyhow::Result<impl Future<Output = ()>> {
|
||||||
where
|
|
||||||
L: Platform,
|
|
||||||
F: Platform,
|
|
||||||
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
|
||||||
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
|
||||||
L::Compiler: 'a,
|
|
||||||
F::Compiler: 'a,
|
|
||||||
{
|
|
||||||
info!("Starting driver task");
|
info!("Starting driver task");
|
||||||
|
|
||||||
let cached_compiler = Arc::new(
|
let cached_compiler = Arc::new(
|
||||||
@@ -327,23 +320,23 @@ where
|
|||||||
let cached_compiler = cached_compiler.clone();
|
let cached_compiler = cached_compiler.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
|
for (platform, node, _, _) in test.platforms.iter() {
|
||||||
test.reporter
|
test.reporter
|
||||||
.report_leader_node_assigned_event(
|
.report_node_assigned_event(
|
||||||
test.leader_node.id(),
|
node.id(),
|
||||||
*L::config_id(),
|
platform.platform_identifier(),
|
||||||
test.leader_node.connection_string(),
|
node.connection_string(),
|
||||||
)
|
|
||||||
.expect("Can't fail");
|
|
||||||
test.reporter
|
|
||||||
.report_follower_node_assigned_event(
|
|
||||||
test.follower_node.id(),
|
|
||||||
*F::config_id(),
|
|
||||||
test.follower_node.connection_string(),
|
|
||||||
)
|
)
|
||||||
.expect("Can't fail");
|
.expect("Can't fail");
|
||||||
|
}
|
||||||
|
|
||||||
|
let private_key_allocator = Arc::new(Mutex::new(PrivateKeyAllocator::new(
|
||||||
|
context.wallet_configuration.highest_private_key_exclusive(),
|
||||||
|
)));
|
||||||
|
|
||||||
let reporter = test.reporter.clone();
|
let reporter = test.reporter.clone();
|
||||||
let result = handle_case_driver::<L, F>(test, cached_compiler).await;
|
let result =
|
||||||
|
handle_case_driver(&test, cached_compiler, private_key_allocator).await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(steps_executed) => reporter
|
Ok(steps_executed) => reporter
|
||||||
@@ -449,62 +442,56 @@ async fn start_cli_reporting_task(reporter: Reporter) {
|
|||||||
mode = %test.mode,
|
mode = %test.mode,
|
||||||
case_idx = %test.case_idx,
|
case_idx = %test.case_idx,
|
||||||
case_name = test.case.name.as_deref().unwrap_or("Unnamed Case"),
|
case_name = test.case.name.as_deref().unwrap_or("Unnamed Case"),
|
||||||
leader_node = test.leader_node.id(),
|
|
||||||
follower_node = test.follower_node.id(),
|
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
async fn handle_case_driver<'a, L, F>(
|
async fn handle_case_driver<'a>(
|
||||||
test: Test<'a, L, F>,
|
test: &Test<'a>,
|
||||||
cached_compiler: Arc<CachedCompiler<'a>>,
|
cached_compiler: Arc<CachedCompiler<'a>>,
|
||||||
) -> anyhow::Result<usize>
|
private_key_allocator: Arc<Mutex<PrivateKeyAllocator>>,
|
||||||
where
|
) -> anyhow::Result<usize> {
|
||||||
L: Platform,
|
let platform_state = stream::iter(test.platforms.iter())
|
||||||
F: Platform,
|
// Compiling the pre-link contracts.
|
||||||
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
.filter_map(|(platform, node, compiler, reporter)| {
|
||||||
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
let cached_compiler = cached_compiler.clone();
|
||||||
L::Compiler: 'a,
|
|
||||||
F::Compiler: 'a,
|
|
||||||
{
|
|
||||||
let leader_reporter = test
|
|
||||||
.reporter
|
|
||||||
.execution_specific_reporter(test.leader_node.id(), NodeDesignation::Leader);
|
|
||||||
let follower_reporter = test
|
|
||||||
.reporter
|
|
||||||
.execution_specific_reporter(test.follower_node.id(), NodeDesignation::Follower);
|
|
||||||
|
|
||||||
let (
|
async move {
|
||||||
CompilerOutput {
|
let compiler_output = cached_compiler
|
||||||
contracts: leader_pre_link_contracts,
|
.compile_contracts(
|
||||||
},
|
|
||||||
CompilerOutput {
|
|
||||||
contracts: follower_pre_link_contracts,
|
|
||||||
},
|
|
||||||
) = try_join!(
|
|
||||||
cached_compiler.compile_contracts::<L>(
|
|
||||||
test.metadata,
|
test.metadata,
|
||||||
test.metadata_file_path,
|
test.metadata_file_path,
|
||||||
test.mode.clone(),
|
test.mode.clone(),
|
||||||
None,
|
None,
|
||||||
&test.leader_compiler,
|
compiler.as_ref(),
|
||||||
&leader_reporter,
|
*platform,
|
||||||
),
|
reporter,
|
||||||
cached_compiler.compile_contracts::<F>(
|
|
||||||
test.metadata,
|
|
||||||
test.metadata_file_path,
|
|
||||||
test.mode.clone(),
|
|
||||||
None,
|
|
||||||
&test.follower_compiler,
|
|
||||||
&follower_reporter
|
|
||||||
)
|
)
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
error!(
|
||||||
|
?err,
|
||||||
|
platform_identifier = %platform.platform_identifier(),
|
||||||
|
"Pre-linking compilation failed"
|
||||||
)
|
)
|
||||||
.context("Failed to compile pre-link contracts for leader/follower in parallel")?;
|
})
|
||||||
|
.ok()?;
|
||||||
let mut leader_deployed_libraries = None::<HashMap<_, _>>;
|
Some((test, platform, node, compiler, reporter, compiler_output))
|
||||||
let mut follower_deployed_libraries = None::<HashMap<_, _>>;
|
}
|
||||||
|
})
|
||||||
|
// Deploying the libraries for the platform.
|
||||||
|
.filter_map(
|
||||||
|
|(test, platform, node, compiler, reporter, compiler_output)| async move {
|
||||||
|
let mut deployed_libraries = None::<HashMap<_, _>>;
|
||||||
let mut contract_sources = test
|
let mut contract_sources = test
|
||||||
.metadata
|
.metadata
|
||||||
.contract_sources()
|
.contract_sources()
|
||||||
.context("Failed to retrieve contract sources from metadata")?;
|
.inspect_err(|err| {
|
||||||
|
error!(
|
||||||
|
?err,
|
||||||
|
platform_identifier = %platform.platform_identifier(),
|
||||||
|
"Failed to retrieve contract sources from metadata"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
for library_instance in test
|
for library_instance in test
|
||||||
.metadata
|
.metadata
|
||||||
.libraries
|
.libraries
|
||||||
@@ -517,162 +504,117 @@ where
|
|||||||
let ContractPathAndIdent {
|
let ContractPathAndIdent {
|
||||||
contract_source_path: library_source_path,
|
contract_source_path: library_source_path,
|
||||||
contract_ident: library_ident,
|
contract_ident: library_ident,
|
||||||
} = contract_sources
|
} = contract_sources.remove(library_instance)?;
|
||||||
.remove(library_instance)
|
|
||||||
.context("Failed to find the contract source")?;
|
|
||||||
|
|
||||||
let (leader_code, leader_abi) = leader_pre_link_contracts
|
let (code, abi) = compiler_output
|
||||||
|
.contracts
|
||||||
.get(&library_source_path)
|
.get(&library_source_path)
|
||||||
.and_then(|contracts| contracts.get(library_ident.as_str()))
|
.and_then(|contracts| contracts.get(library_ident.as_str()))?;
|
||||||
.context("Declared library was not compiled")?;
|
|
||||||
let (follower_code, follower_abi) = follower_pre_link_contracts
|
|
||||||
.get(&library_source_path)
|
|
||||||
.and_then(|contracts| contracts.get(library_ident.as_str()))
|
|
||||||
.context("Declared library was not compiled")?;
|
|
||||||
|
|
||||||
let leader_code = match alloy::hex::decode(leader_code) {
|
let code = alloy::hex::decode(code).ok()?;
|
||||||
Ok(code) => code,
|
|
||||||
Err(error) => {
|
|
||||||
anyhow::bail!("Failed to hex-decode the byte code {}", error)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let follower_code = match alloy::hex::decode(follower_code) {
|
|
||||||
Ok(code) => code,
|
|
||||||
Err(error) => {
|
|
||||||
anyhow::bail!("Failed to hex-decode the byte code {}", error)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Getting the deployer address from the cases themselves. This is to ensure that we're
|
// Getting the deployer address from the cases themselves. This is to ensure
|
||||||
// doing the deployments from different accounts and therefore we're not slowed down by
|
// that we're doing the deployments from different accounts and therefore we're
|
||||||
// the nonce.
|
// not slowed down by the nonce.
|
||||||
let deployer_address = test
|
let deployer_address = test
|
||||||
.case
|
.case
|
||||||
.steps
|
.steps
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|step| match step {
|
.filter_map(|step| match step {
|
||||||
Step::FunctionCall(input) => Some(input.caller),
|
Step::FunctionCall(input) => input.caller.as_address().copied(),
|
||||||
Step::BalanceAssertion(..) => None,
|
Step::BalanceAssertion(..) => None,
|
||||||
Step::StorageEmptyAssertion(..) => None,
|
Step::StorageEmptyAssertion(..) => None,
|
||||||
|
Step::Repeat(..) => None,
|
||||||
|
Step::AllocateAccount(..) => None,
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
.unwrap_or(Input::default_caller());
|
.unwrap_or(FunctionCallStep::default_caller_address());
|
||||||
let leader_tx = TransactionBuilder::<Ethereum>::with_deploy_code(
|
let tx = TransactionBuilder::<Ethereum>::with_deploy_code(
|
||||||
TransactionRequest::default().from(deployer_address),
|
TransactionRequest::default().from(deployer_address),
|
||||||
leader_code,
|
code,
|
||||||
);
|
);
|
||||||
let follower_tx = TransactionBuilder::<Ethereum>::with_deploy_code(
|
let receipt = node
|
||||||
TransactionRequest::default().from(deployer_address),
|
.execute_transaction(tx)
|
||||||
follower_code,
|
.await
|
||||||
);
|
.inspect_err(|err| {
|
||||||
|
error!(
|
||||||
let (leader_receipt, follower_receipt) = try_join!(
|
?err,
|
||||||
test.leader_node.execute_transaction(leader_tx),
|
%library_instance,
|
||||||
test.follower_node.execute_transaction(follower_tx)
|
platform_identifier = %platform.platform_identifier(),
|
||||||
)?;
|
"Failed to deploy the library"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
?library_instance,
|
?library_instance,
|
||||||
library_address = ?leader_receipt.contract_address,
|
platform_identifier = %platform.platform_identifier(),
|
||||||
"Deployed library to leader"
|
"Deployed library"
|
||||||
);
|
|
||||||
debug!(
|
|
||||||
?library_instance,
|
|
||||||
library_address = ?follower_receipt.contract_address,
|
|
||||||
"Deployed library to follower"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let leader_library_address = leader_receipt
|
let library_address = receipt.contract_address?;
|
||||||
.contract_address
|
|
||||||
.context("Contract deployment didn't return an address")?;
|
|
||||||
let follower_library_address = follower_receipt
|
|
||||||
.contract_address
|
|
||||||
.context("Contract deployment didn't return an address")?;
|
|
||||||
|
|
||||||
leader_deployed_libraries.get_or_insert_default().insert(
|
deployed_libraries.get_or_insert_default().insert(
|
||||||
library_instance.clone(),
|
library_instance.clone(),
|
||||||
(
|
(library_ident.clone(), library_address, abi.clone()),
|
||||||
library_ident.clone(),
|
|
||||||
leader_library_address,
|
|
||||||
leader_abi.clone(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
follower_deployed_libraries.get_or_insert_default().insert(
|
|
||||||
library_instance.clone(),
|
|
||||||
(
|
|
||||||
library_ident,
|
|
||||||
follower_library_address,
|
|
||||||
follower_abi.clone(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(ref leader_deployed_libraries) = leader_deployed_libraries {
|
|
||||||
leader_reporter.report_libraries_deployed_event(
|
|
||||||
leader_deployed_libraries
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.map(|(key, (_, address, _))| (key, address))
|
|
||||||
.collect::<BTreeMap<_, _>>(),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
if let Some(ref follower_deployed_libraries) = follower_deployed_libraries {
|
|
||||||
follower_reporter.report_libraries_deployed_event(
|
|
||||||
follower_deployed_libraries
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.map(|(key, (_, address, _))| (key, address))
|
|
||||||
.collect::<BTreeMap<_, _>>(),
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (
|
Some((
|
||||||
CompilerOutput {
|
test,
|
||||||
contracts: leader_post_link_contracts,
|
platform,
|
||||||
|
node,
|
||||||
|
compiler,
|
||||||
|
reporter,
|
||||||
|
compiler_output,
|
||||||
|
deployed_libraries,
|
||||||
|
))
|
||||||
},
|
},
|
||||||
CompilerOutput {
|
)
|
||||||
contracts: follower_post_link_contracts,
|
// Compiling the post-link contracts.
|
||||||
},
|
.filter_map(
|
||||||
) = try_join!(
|
|(test, platform, node, compiler, reporter, _, deployed_libraries)| {
|
||||||
cached_compiler.compile_contracts::<L>(
|
let cached_compiler = cached_compiler.clone();
|
||||||
|
let private_key_allocator = private_key_allocator.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
let compiler_output = cached_compiler
|
||||||
|
.compile_contracts(
|
||||||
test.metadata,
|
test.metadata,
|
||||||
test.metadata_file_path,
|
test.metadata_file_path,
|
||||||
test.mode.clone(),
|
test.mode.clone(),
|
||||||
leader_deployed_libraries.as_ref(),
|
deployed_libraries.as_ref(),
|
||||||
&test.leader_compiler,
|
compiler.as_ref(),
|
||||||
&leader_reporter,
|
*platform,
|
||||||
),
|
reporter,
|
||||||
cached_compiler.compile_contracts::<F>(
|
|
||||||
test.metadata,
|
|
||||||
test.metadata_file_path,
|
|
||||||
test.mode.clone(),
|
|
||||||
follower_deployed_libraries.as_ref(),
|
|
||||||
&test.follower_compiler,
|
|
||||||
&follower_reporter
|
|
||||||
)
|
)
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
error!(
|
||||||
|
?err,
|
||||||
|
platform_identifier = %platform.platform_identifier(),
|
||||||
|
"Pre-linking compilation failed"
|
||||||
)
|
)
|
||||||
.context("Failed to compile post-link contracts for leader/follower in parallel")?;
|
})
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
let leader_state = CaseState::<L>::new(
|
let case_state = CaseState::new(
|
||||||
test.leader_compiler.version().clone(),
|
compiler.version().clone(),
|
||||||
leader_post_link_contracts,
|
compiler_output.contracts,
|
||||||
leader_deployed_libraries.unwrap_or_default(),
|
deployed_libraries.unwrap_or_default(),
|
||||||
leader_reporter,
|
reporter.clone(),
|
||||||
);
|
private_key_allocator,
|
||||||
let follower_state = CaseState::<F>::new(
|
|
||||||
test.follower_compiler.version().clone(),
|
|
||||||
follower_post_link_contracts,
|
|
||||||
follower_deployed_libraries.unwrap_or_default(),
|
|
||||||
follower_reporter,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut driver = CaseDriver::<L, F>::new(
|
Some((*node, platform.platform_identifier(), case_state))
|
||||||
test.metadata,
|
}
|
||||||
test.case,
|
},
|
||||||
test.leader_node,
|
)
|
||||||
test.follower_node,
|
// Collect
|
||||||
leader_state,
|
.collect::<Vec<_>>()
|
||||||
follower_state,
|
.await;
|
||||||
);
|
|
||||||
|
let mut driver = CaseDriver::new(test.metadata, test.case, platform_state);
|
||||||
driver
|
driver
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
@@ -680,41 +622,43 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn execute_corpus(
|
async fn execute_corpus(
|
||||||
context: ExecutionContext,
|
context: TestExecutionContext,
|
||||||
tests: &[MetadataFile],
|
tests: &[MetadataFile],
|
||||||
reporter: Reporter,
|
reporter: Reporter,
|
||||||
report_aggregator_task: impl Future<Output = anyhow::Result<()>>,
|
report_aggregator_task: impl Future<Output = anyhow::Result<()>>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
match (&context.leader, &context.follower) {
|
let platforms = context
|
||||||
(TestingPlatform::Geth, TestingPlatform::Kitchensink) => {
|
.platforms
|
||||||
run_driver::<Geth, Kitchensink>(context, tests, reporter, report_aggregator_task)
|
.iter()
|
||||||
.await?
|
.copied()
|
||||||
}
|
.collect::<BTreeSet<_>>()
|
||||||
(TestingPlatform::Geth, TestingPlatform::Geth) => {
|
.into_iter()
|
||||||
run_driver::<Geth, Geth>(context, tests, reporter, report_aggregator_task).await?
|
.map(Into::<&dyn Platform>::into)
|
||||||
}
|
.collect::<Vec<_>>();
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
run_driver(context, tests, reporter, report_aggregator_task, platforms).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// this represents a single "test"; a mode, path and collection of cases.
|
/// this represents a single "test"; a mode, path and collection of cases.
|
||||||
#[derive(Clone)]
|
#[allow(clippy::type_complexity)]
|
||||||
struct Test<'a, L: Platform, F: Platform> {
|
struct Test<'a> {
|
||||||
metadata: &'a MetadataFile,
|
metadata: &'a MetadataFile,
|
||||||
metadata_file_path: &'a Path,
|
metadata_file_path: &'a Path,
|
||||||
mode: Cow<'a, Mode>,
|
mode: Cow<'a, Mode>,
|
||||||
case_idx: CaseIdx,
|
case_idx: CaseIdx,
|
||||||
case: &'a Case,
|
case: &'a Case,
|
||||||
leader_node: &'a <L as Platform>::Blockchain,
|
platforms: Vec<(
|
||||||
follower_node: &'a <F as Platform>::Blockchain,
|
&'a dyn Platform,
|
||||||
leader_compiler: L::Compiler,
|
&'a dyn EthereumNode,
|
||||||
follower_compiler: F::Compiler,
|
Box<dyn SolidityCompiler>,
|
||||||
|
ExecutionSpecificReporter,
|
||||||
|
)>,
|
||||||
reporter: TestSpecificReporter,
|
reporter: TestSpecificReporter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, L: Platform, F: Platform> Test<'a, L, F> {
|
impl<'a> Test<'a> {
|
||||||
/// Checks if this test can be ran with the current configuration.
|
/// Checks if this test can be ran with the current configuration.
|
||||||
pub fn check_compatibility(&self) -> TestCheckFunctionResult {
|
pub fn check_compatibility(&self) -> TestCheckFunctionResult {
|
||||||
self.check_metadata_file_ignored()?;
|
self.check_metadata_file_ignored()?;
|
||||||
@@ -743,74 +687,94 @@ impl<'a, L: Platform, F: Platform> Test<'a, L, F> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the leader and the follower both support the desired targets in the metadata file.
|
/// Checks if the platforms all support the desired targets in the metadata file.
|
||||||
fn check_target_compatibility(&self) -> TestCheckFunctionResult {
|
fn check_target_compatibility(&self) -> TestCheckFunctionResult {
|
||||||
let leader_support =
|
let mut error_map = indexmap! {
|
||||||
<L::Blockchain as Node>::matches_target(self.metadata.targets.as_deref());
|
"test_desired_targets" => json!(self.metadata.targets.as_ref()),
|
||||||
let follower_support =
|
};
|
||||||
<F::Blockchain as Node>::matches_target(self.metadata.targets.as_deref());
|
let mut is_allowed = true;
|
||||||
let is_allowed = leader_support && follower_support;
|
for (platform, ..) in self.platforms.iter() {
|
||||||
|
let is_allowed_for_platform = match self.metadata.targets.as_ref() {
|
||||||
|
None => true,
|
||||||
|
Some(targets) => {
|
||||||
|
let mut target_matches = false;
|
||||||
|
for target in targets.iter() {
|
||||||
|
if &platform.vm_identifier() == target {
|
||||||
|
target_matches = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target_matches
|
||||||
|
}
|
||||||
|
};
|
||||||
|
is_allowed &= is_allowed_for_platform;
|
||||||
|
error_map.insert(
|
||||||
|
platform.platform_identifier().into(),
|
||||||
|
json!(is_allowed_for_platform),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if is_allowed {
|
if is_allowed {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err((
|
Err((
|
||||||
"Either the leader or the follower do not support the target desired by the test.",
|
"One of the platforms do do not support the targets allowed by the test.",
|
||||||
indexmap! {
|
error_map,
|
||||||
"test_desired_targets" => json!(self.metadata.targets.as_ref()),
|
|
||||||
"leader_support" => json!(leader_support),
|
|
||||||
"follower_support" => json!(follower_support),
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks for the compatibility of the EVM version with the leader and follower nodes.
|
// Checks for the compatibility of the EVM version with the platforms specified.
|
||||||
fn check_evm_version_compatibility(&self) -> TestCheckFunctionResult {
|
fn check_evm_version_compatibility(&self) -> TestCheckFunctionResult {
|
||||||
let Some(evm_version_requirement) = self.metadata.required_evm_version else {
|
let Some(evm_version_requirement) = self.metadata.required_evm_version else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let leader_support = evm_version_requirement
|
let mut error_map = indexmap! {
|
||||||
.matches(&<L::Blockchain as revive_dt_node::Node>::evm_version());
|
"test_desired_evm_version" => json!(self.metadata.required_evm_version),
|
||||||
let follower_support = evm_version_requirement
|
};
|
||||||
.matches(&<F::Blockchain as revive_dt_node::Node>::evm_version());
|
let mut is_allowed = true;
|
||||||
let is_allowed = leader_support && follower_support;
|
for (platform, node, ..) in self.platforms.iter() {
|
||||||
|
let is_allowed_for_platform = evm_version_requirement.matches(&node.evm_version());
|
||||||
|
is_allowed &= is_allowed_for_platform;
|
||||||
|
error_map.insert(
|
||||||
|
platform.platform_identifier().into(),
|
||||||
|
json!(is_allowed_for_platform),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if is_allowed {
|
if is_allowed {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err((
|
Err((
|
||||||
"EVM version is incompatible with either the leader or the follower.",
|
"EVM version is incompatible for the platforms specified",
|
||||||
indexmap! {
|
error_map,
|
||||||
"test_desired_evm_version" => json!(self.metadata.required_evm_version),
|
|
||||||
"leader_support" => json!(leader_support),
|
|
||||||
"follower_support" => json!(follower_support),
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the leader and follower compilers support the mode that the test is for.
|
/// Checks if the platforms compilers support the mode that the test is for.
|
||||||
fn check_compiler_compatibility(&self) -> TestCheckFunctionResult {
|
fn check_compiler_compatibility(&self) -> TestCheckFunctionResult {
|
||||||
let leader_support = self
|
let mut error_map = indexmap! {
|
||||||
.leader_compiler
|
"test_desired_evm_version" => json!(self.metadata.required_evm_version),
|
||||||
.supports_mode(self.mode.optimize_setting, self.mode.pipeline);
|
};
|
||||||
let follower_support = self
|
let mut is_allowed = true;
|
||||||
.follower_compiler
|
for (platform, _, compiler, ..) in self.platforms.iter() {
|
||||||
.supports_mode(self.mode.optimize_setting, self.mode.pipeline);
|
let is_allowed_for_platform =
|
||||||
let is_allowed = leader_support && follower_support;
|
compiler.supports_mode(self.mode.optimize_setting, self.mode.pipeline);
|
||||||
|
is_allowed &= is_allowed_for_platform;
|
||||||
|
error_map.insert(
|
||||||
|
platform.platform_identifier().into(),
|
||||||
|
json!(is_allowed_for_platform),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if is_allowed {
|
if is_allowed {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err((
|
Err((
|
||||||
"Compilers do not support this mode either for the leader or for the follower.",
|
"Compilers do not support this mode either for the provided platforms.",
|
||||||
indexmap! {
|
error_map,
|
||||||
"mode" => json!(self.mode),
|
|
||||||
"leader_support" => json!(leader_support),
|
|
||||||
"follower_support" => json!(follower_support),
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
//! This crate implements concurrent handling of testing node.
|
||||||
|
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use anyhow::Context as _;
|
||||||
|
use revive_dt_config::*;
|
||||||
|
use revive_dt_core::Platform;
|
||||||
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
|
|
||||||
|
/// The node pool starts one or more [Node] which then can be accessed
|
||||||
|
/// in a round robbin fashion.
|
||||||
|
pub struct NodePool {
|
||||||
|
next: AtomicUsize,
|
||||||
|
nodes: Vec<Box<dyn EthereumNode + Send + Sync>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodePool {
|
||||||
|
/// Create a new Pool. This will start as many nodes as there are workers in `config`.
|
||||||
|
pub fn new(context: Context, platform: &dyn Platform) -> anyhow::Result<Self> {
|
||||||
|
let concurrency_configuration = AsRef::<ConcurrencyConfiguration>::as_ref(&context);
|
||||||
|
let nodes = concurrency_configuration.number_of_nodes;
|
||||||
|
|
||||||
|
let mut handles = Vec::with_capacity(nodes);
|
||||||
|
for _ in 0..nodes {
|
||||||
|
let context = context.clone();
|
||||||
|
handles.push(platform.new_node(context)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut nodes = Vec::with_capacity(nodes);
|
||||||
|
for handle in handles {
|
||||||
|
nodes.push(
|
||||||
|
handle
|
||||||
|
.join()
|
||||||
|
.map_err(|error| anyhow::anyhow!("failed to spawn node: {:?}", error))
|
||||||
|
.context("Failed to join node spawn thread")?
|
||||||
|
.map_err(|error| anyhow::anyhow!("node failed to spawn: {error}"))
|
||||||
|
.context("Node failed to spawn")?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
nodes,
|
||||||
|
next: Default::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a handle to the next node.
|
||||||
|
pub fn round_robbin(&self) -> &dyn EthereumNode {
|
||||||
|
let current = self.next.fetch_add(1, Ordering::SeqCst) % self.nodes.len();
|
||||||
|
self.nodes.get(current).unwrap().as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
use revive_dt_common::{macros::define_wrapper_type, types::Mode};
|
use revive_dt_common::{macros::define_wrapper_type, types::Mode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
input::{Expected, Step},
|
|
||||||
mode::ParsedMode,
|
mode::ParsedMode,
|
||||||
|
steps::{Expected, RepeatStep, Step},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq, JsonSchema)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq, JsonSchema)]
|
||||||
@@ -55,7 +55,6 @@ pub struct Case {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Case {
|
impl Case {
|
||||||
#[allow(irrefutable_let_patterns)]
|
|
||||||
pub fn steps_iterator(&self) -> impl Iterator<Item = Step> {
|
pub fn steps_iterator(&self) -> impl Iterator<Item = Step> {
|
||||||
let steps_len = self.steps.len();
|
let steps_len = self.steps.len();
|
||||||
self.steps
|
self.steps
|
||||||
@@ -84,6 +83,24 @@ impl Case {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn steps_iterator_for_benchmarks(
|
||||||
|
&self,
|
||||||
|
default_repeat_count: usize,
|
||||||
|
) -> Box<dyn Iterator<Item = Step> + '_> {
|
||||||
|
let contains_repeat = self
|
||||||
|
.steps_iterator()
|
||||||
|
.any(|step| matches!(&step, Step::Repeat(..)));
|
||||||
|
if contains_repeat {
|
||||||
|
Box::new(self.steps_iterator()) as Box<_>
|
||||||
|
} else {
|
||||||
|
Box::new(std::iter::once(Step::Repeat(Box::new(RepeatStep {
|
||||||
|
comment: None,
|
||||||
|
repeat: default_repeat_count,
|
||||||
|
steps: self.steps_iterator().collect(),
|
||||||
|
})))) as Box<_>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn solc_modes(&self) -> Vec<Mode> {
|
pub fn solc_modes(&self) -> Vec<Mode> {
|
||||||
match &self.modes {
|
match &self.modes {
|
||||||
Some(modes) => ParsedMode::many_to_modes(modes.iter()).collect(),
|
Some(modes) => ParsedMode::many_to_modes(modes.iter()).collect(),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
pub mod case;
|
pub mod case;
|
||||||
pub mod corpus;
|
pub mod corpus;
|
||||||
pub mod input;
|
|
||||||
pub mod metadata;
|
pub mod metadata;
|
||||||
pub mod mode;
|
pub mod mode;
|
||||||
|
pub mod steps;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use revive_common::EVMVersion;
|
use revive_common::EVMVersion;
|
||||||
use revive_dt_common::{
|
use revive_dt_common::{
|
||||||
cached_fs::read_to_string, iterators::FilesWithExtensionIterator, macros::define_wrapper_type,
|
cached_fs::read_to_string,
|
||||||
types::Mode,
|
iterators::FilesWithExtensionIterator,
|
||||||
|
macros::define_wrapper_type,
|
||||||
|
types::{Mode, VmIdentifier},
|
||||||
};
|
};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
@@ -81,7 +83,7 @@ pub struct Metadata {
|
|||||||
/// example, if we wish for the metadata file's cases to only be run on PolkaVM then we'd
|
/// example, if we wish for the metadata file's cases to only be run on PolkaVM then we'd
|
||||||
/// specify a target of "PolkaVM" in here.
|
/// specify a target of "PolkaVM" in here.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub targets: Option<Vec<String>>,
|
pub targets: Option<Vec<VmIdentifier>>,
|
||||||
|
|
||||||
/// A vector of the test cases and workloads contained within the metadata file. This is their
|
/// A vector of the test cases and workloads contained within the metadata file. This is their
|
||||||
/// primary description.
|
/// primary description.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, fmt::Display, str::FromStr};
|
||||||
|
|
||||||
use alloy::{
|
use alloy::{
|
||||||
eips::BlockNumberOrTag,
|
eips::BlockNumberOrTag,
|
||||||
@@ -28,33 +28,104 @@ use crate::{metadata::ContractInstance, traits::ResolutionContext};
|
|||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Step {
|
pub enum Step {
|
||||||
/// A function call or an invocation to some function on some smart contract.
|
/// A function call or an invocation to some function on some smart contract.
|
||||||
FunctionCall(Box<Input>),
|
FunctionCall(Box<FunctionCallStep>),
|
||||||
|
|
||||||
/// A step for performing a balance assertion on some account or contract.
|
/// A step for performing a balance assertion on some account or contract.
|
||||||
BalanceAssertion(Box<BalanceAssertion>),
|
BalanceAssertion(Box<BalanceAssertionStep>),
|
||||||
|
|
||||||
/// A step for asserting that the storage of some contract or account is empty.
|
/// A step for asserting that the storage of some contract or account is empty.
|
||||||
StorageEmptyAssertion(Box<StorageEmptyAssertion>),
|
StorageEmptyAssertion(Box<StorageEmptyAssertionStep>),
|
||||||
|
|
||||||
|
/// A special step for repeating a bunch of steps a certain number of times.
|
||||||
|
Repeat(Box<RepeatStep>),
|
||||||
|
|
||||||
|
/// A step type that allows for a new account address to be allocated and to later on be used
|
||||||
|
/// as the caller in another step.
|
||||||
|
AllocateAccount(Box<AllocateAccountStep>),
|
||||||
}
|
}
|
||||||
|
|
||||||
define_wrapper_type!(
|
define_wrapper_type!(
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct StepIdx(usize) impl Display;
|
pub struct StepIdx(usize) impl Display, FromStr;
|
||||||
);
|
);
|
||||||
|
|
||||||
|
define_wrapper_type!(
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
#[serde(try_from = "String", into = "String")]
|
||||||
|
pub struct StepPath(Vec<StepIdx>);
|
||||||
|
);
|
||||||
|
|
||||||
|
impl StepPath {
|
||||||
|
pub fn from_iterator(path: impl IntoIterator<Item = impl Into<StepIdx>>) -> Self {
|
||||||
|
Self(path.into_iter().map(|value| value.into()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn increment(&self) -> Self {
|
||||||
|
let mut this = self.clone();
|
||||||
|
if let Some(last) = this.last_mut() {
|
||||||
|
last.0 += 1
|
||||||
|
}
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append(&self, step_idx: impl Into<StepIdx>) -> Self {
|
||||||
|
let mut this = self.clone();
|
||||||
|
this.0.push(step_idx.into());
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for StepPath {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.map(|idx| idx.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(".")
|
||||||
|
.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for StepPath {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
s.split(".")
|
||||||
|
.map(StepIdx::from_str)
|
||||||
|
.collect::<anyhow::Result<Vec<_>>>()
|
||||||
|
.map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StepPath> for String {
|
||||||
|
fn from(value: StepPath) -> Self {
|
||||||
|
value.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for StepPath {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
value.parse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This is an input step which is a transaction description that the framework translates into a
|
/// This is an input step which is a transaction description that the framework translates into a
|
||||||
/// transaction and executes on the nodes.
|
/// transaction and executes on the nodes.
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
||||||
pub struct Input {
|
pub struct FunctionCallStep {
|
||||||
/// The address of the account performing the call and paying the fees for it.
|
/// The address of the account performing the call and paying the fees for it.
|
||||||
#[serde(default = "Input::default_caller")]
|
#[serde(default = "FunctionCallStep::default_caller")]
|
||||||
#[schemars(with = "String")]
|
#[schemars(with = "String")]
|
||||||
pub caller: Address,
|
pub caller: StepAddress,
|
||||||
|
|
||||||
/// An optional comment on the step which has no impact on the execution in any way.
|
/// An optional comment on the step which has no impact on the execution in any way.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
|
|
||||||
/// The contract instance that's being called in this transaction step.
|
/// The contract instance that's being called in this transaction step.
|
||||||
#[serde(default = "Input::default_instance")]
|
#[serde(default = "FunctionCallStep::default_instance")]
|
||||||
pub instance: ContractInstance,
|
pub instance: ContractInstance,
|
||||||
|
|
||||||
/// The method that's being called in this step.
|
/// The method that's being called in this step.
|
||||||
@@ -84,8 +155,8 @@ pub struct Input {
|
|||||||
|
|
||||||
/// This represents a balance assertion step where the framework needs to query the balance of some
|
/// This represents a balance assertion step where the framework needs to query the balance of some
|
||||||
/// account or contract and assert that it's some amount.
|
/// account or contract and assert that it's some amount.
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
||||||
pub struct BalanceAssertion {
|
pub struct BalanceAssertionStep {
|
||||||
/// An optional comment on the balance assertion.
|
/// An optional comment on the balance assertion.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
@@ -96,7 +167,7 @@ pub struct BalanceAssertion {
|
|||||||
/// this could be a normal hex address, a variable such as `Test.address`, or perhaps even a
|
/// this could be a normal hex address, a variable such as `Test.address`, or perhaps even a
|
||||||
/// full on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are
|
/// full on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are
|
||||||
/// followed in the calldata.
|
/// followed in the calldata.
|
||||||
pub address: String,
|
pub address: StepAddress,
|
||||||
|
|
||||||
/// The amount of balance to assert that the account or contract has. This is a 256 bit string
|
/// The amount of balance to assert that the account or contract has. This is a 256 bit string
|
||||||
/// that's serialized and deserialized into a decimal string.
|
/// that's serialized and deserialized into a decimal string.
|
||||||
@@ -104,8 +175,10 @@ pub struct BalanceAssertion {
|
|||||||
pub expected_balance: U256,
|
pub expected_balance: U256,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
/// This represents an assertion for the storage of some contract or account and whether it's empty
|
||||||
pub struct StorageEmptyAssertion {
|
/// or not.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
||||||
|
pub struct StorageEmptyAssertionStep {
|
||||||
/// An optional comment on the storage empty assertion.
|
/// An optional comment on the storage empty assertion.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
@@ -116,12 +189,40 @@ pub struct StorageEmptyAssertion {
|
|||||||
/// this could be a normal hex address, a variable such as `Test.address`, or perhaps even a
|
/// this could be a normal hex address, a variable such as `Test.address`, or perhaps even a
|
||||||
/// full on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are
|
/// full on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are
|
||||||
/// followed in the calldata.
|
/// followed in the calldata.
|
||||||
pub address: String,
|
pub address: StepAddress,
|
||||||
|
|
||||||
/// A boolean of whether the storage of the address is empty or not.
|
/// A boolean of whether the storage of the address is empty or not.
|
||||||
pub is_storage_empty: bool,
|
pub is_storage_empty: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This represents a repetition step which is a special step type that allows for a sequence of
|
||||||
|
/// steps to be repeated (on different drivers) a certain number of times.
|
||||||
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
||||||
|
pub struct RepeatStep {
|
||||||
|
/// An optional comment on the repetition step.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub comment: Option<String>,
|
||||||
|
|
||||||
|
/// The number of repetitions that the steps should be repeated for.
|
||||||
|
pub repeat: usize,
|
||||||
|
|
||||||
|
/// The sequence of steps to repeat for the above defined number of repetitions.
|
||||||
|
pub steps: Vec<Step>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
||||||
|
pub struct AllocateAccountStep {
|
||||||
|
/// An optional comment on the account allocation step.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub comment: Option<String>,
|
||||||
|
|
||||||
|
/// An instruction to allocate a new account with the value being the variable name of that
|
||||||
|
/// account. This must start with `$VARIABLE:` and then be followed by the variable name of the
|
||||||
|
/// account.
|
||||||
|
#[serde(rename = "allocate_account")]
|
||||||
|
pub variable_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// A set of expectations and assertions to make about the transaction after it ran.
|
/// A set of expectations and assertions to make about the transaction after it ran.
|
||||||
///
|
///
|
||||||
/// If this is not specified then the only assertion that will be ran is that the transaction
|
/// If this is not specified then the only assertion that will be ran is that the transaction
|
||||||
@@ -162,7 +263,7 @@ pub struct ExpectedOutput {
|
|||||||
pub struct Event {
|
pub struct Event {
|
||||||
/// An optional field of the address of the emitter of the event.
|
/// An optional field of the address of the emitter of the event.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub address: Option<String>,
|
pub address: Option<StepAddress>,
|
||||||
|
|
||||||
/// The set of topics to expect the event to have.
|
/// The set of topics to expect the event to have.
|
||||||
pub topics: Vec<String>,
|
pub topics: Vec<String>,
|
||||||
@@ -295,20 +396,81 @@ pub struct VariableAssignments {
|
|||||||
pub return_data: Vec<String>,
|
pub return_data: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Input {
|
/// An address type that might either be an address literal or a resolvable address.
|
||||||
pub const fn default_caller() -> Address {
|
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
||||||
|
#[schemars(with = "String")]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum StepAddress {
|
||||||
|
Address(Address),
|
||||||
|
ResolvableAddress(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for StepAddress {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Address(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for StepAddress {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
StepAddress::Address(address) => Display::fmt(address, f),
|
||||||
|
StepAddress::ResolvableAddress(address) => Display::fmt(address, f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StepAddress {
|
||||||
|
pub fn as_address(&self) -> Option<&Address> {
|
||||||
|
match self {
|
||||||
|
StepAddress::Address(address) => Some(address),
|
||||||
|
StepAddress::ResolvableAddress(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_resolvable_address(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
StepAddress::ResolvableAddress(address) => Some(address),
|
||||||
|
StepAddress::Address(..) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn resolve_address(
|
||||||
|
&self,
|
||||||
|
resolver: &(impl ResolverApi + ?Sized),
|
||||||
|
context: ResolutionContext<'_>,
|
||||||
|
) -> anyhow::Result<Address> {
|
||||||
|
match self {
|
||||||
|
StepAddress::Address(address) => Ok(*address),
|
||||||
|
StepAddress::ResolvableAddress(address) => Ok(Address::from_slice(
|
||||||
|
Calldata::new_compound([address])
|
||||||
|
.calldata(resolver, context)
|
||||||
|
.await?
|
||||||
|
.get(12..32)
|
||||||
|
.expect("Can't fail"),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionCallStep {
|
||||||
|
pub const fn default_caller_address() -> Address {
|
||||||
Address(FixedBytes(alloy::hex!(
|
Address(FixedBytes(alloy::hex!(
|
||||||
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"
|
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn default_caller() -> StepAddress {
|
||||||
|
StepAddress::Address(Self::default_caller_address())
|
||||||
|
}
|
||||||
|
|
||||||
fn default_instance() -> ContractInstance {
|
fn default_instance() -> ContractInstance {
|
||||||
ContractInstance::new("Test")
|
ContractInstance::new("Test")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn encoded_input(
|
pub async fn encoded_input(
|
||||||
&self,
|
&self,
|
||||||
resolver: &impl ResolverApi,
|
resolver: &(impl ResolverApi + ?Sized),
|
||||||
context: ResolutionContext<'_>,
|
context: ResolutionContext<'_>,
|
||||||
) -> anyhow::Result<Bytes> {
|
) -> anyhow::Result<Bytes> {
|
||||||
match self.method {
|
match self.method {
|
||||||
@@ -377,14 +539,15 @@ impl Input {
|
|||||||
/// Parse this input into a legacy transaction.
|
/// Parse this input into a legacy transaction.
|
||||||
pub async fn legacy_transaction(
|
pub async fn legacy_transaction(
|
||||||
&self,
|
&self,
|
||||||
resolver: &impl ResolverApi,
|
resolver: &(impl ResolverApi + ?Sized),
|
||||||
context: ResolutionContext<'_>,
|
context: ResolutionContext<'_>,
|
||||||
) -> anyhow::Result<TransactionRequest> {
|
) -> anyhow::Result<TransactionRequest> {
|
||||||
let input_data = self
|
let input_data = self
|
||||||
.encoded_input(resolver, context)
|
.encoded_input(resolver, context)
|
||||||
.await
|
.await
|
||||||
.context("Failed to encode input bytes for transaction request")?;
|
.context("Failed to encode input bytes for transaction request")?;
|
||||||
let transaction_request = TransactionRequest::default().from(self.caller).value(
|
let caller = self.caller.resolve_address(resolver, context).await?;
|
||||||
|
let transaction_request = TransactionRequest::default().from(caller).value(
|
||||||
self.value
|
self.value
|
||||||
.map(|value| value.into_inner())
|
.map(|value| value.into_inner())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
@@ -466,7 +629,7 @@ impl Calldata {
|
|||||||
|
|
||||||
pub async fn calldata(
|
pub async fn calldata(
|
||||||
&self,
|
&self,
|
||||||
resolver: &impl ResolverApi,
|
resolver: &(impl ResolverApi + ?Sized),
|
||||||
context: ResolutionContext<'_>,
|
context: ResolutionContext<'_>,
|
||||||
) -> anyhow::Result<Vec<u8>> {
|
) -> anyhow::Result<Vec<u8>> {
|
||||||
let mut buffer = Vec::<u8>::with_capacity(self.size_requirement());
|
let mut buffer = Vec::<u8>::with_capacity(self.size_requirement());
|
||||||
@@ -478,7 +641,7 @@ impl Calldata {
|
|||||||
pub async fn calldata_into_slice(
|
pub async fn calldata_into_slice(
|
||||||
&self,
|
&self,
|
||||||
buffer: &mut Vec<u8>,
|
buffer: &mut Vec<u8>,
|
||||||
resolver: &impl ResolverApi,
|
resolver: &(impl ResolverApi + ?Sized),
|
||||||
context: ResolutionContext<'_>,
|
context: ResolutionContext<'_>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
@@ -515,7 +678,7 @@ impl Calldata {
|
|||||||
pub async fn is_equivalent(
|
pub async fn is_equivalent(
|
||||||
&self,
|
&self,
|
||||||
other: &[u8],
|
other: &[u8],
|
||||||
resolver: &impl ResolverApi,
|
resolver: &(impl ResolverApi + ?Sized),
|
||||||
context: ResolutionContext<'_>,
|
context: ResolutionContext<'_>,
|
||||||
) -> anyhow::Result<bool> {
|
) -> anyhow::Result<bool> {
|
||||||
match self {
|
match self {
|
||||||
@@ -557,7 +720,7 @@ impl CalldataItem {
|
|||||||
#[instrument(level = "info", skip_all, err)]
|
#[instrument(level = "info", skip_all, err)]
|
||||||
async fn resolve(
|
async fn resolve(
|
||||||
&self,
|
&self,
|
||||||
resolver: &impl ResolverApi,
|
resolver: &(impl ResolverApi + ?Sized),
|
||||||
context: ResolutionContext<'_>,
|
context: ResolutionContext<'_>,
|
||||||
) -> anyhow::Result<U256> {
|
) -> anyhow::Result<U256> {
|
||||||
let mut stack = Vec::<CalldataToken<U256>>::new();
|
let mut stack = Vec::<CalldataToken<U256>>::new();
|
||||||
@@ -662,7 +825,7 @@ impl<T: AsRef<str>> CalldataToken<T> {
|
|||||||
/// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146
|
/// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146
|
||||||
async fn resolve(
|
async fn resolve(
|
||||||
self,
|
self,
|
||||||
resolver: &impl ResolverApi,
|
resolver: &(impl ResolverApi + ?Sized),
|
||||||
context: ResolutionContext<'_>,
|
context: ResolutionContext<'_>,
|
||||||
) -> anyhow::Result<CalldataToken<U256>> {
|
) -> anyhow::Result<CalldataToken<U256>> {
|
||||||
match self {
|
match self {
|
||||||
@@ -695,7 +858,7 @@ impl<T: AsRef<str>> CalldataToken<T> {
|
|||||||
context
|
context
|
||||||
.transaction_hash()
|
.transaction_hash()
|
||||||
.context("No transaction hash provided to get the transaction gas price")
|
.context("No transaction hash provided to get the transaction gas price")
|
||||||
.map(|tx_hash| resolver.transaction_gas_price(tx_hash))?
|
.map(|tx_hash| resolver.transaction_gas_price(*tx_hash))?
|
||||||
.await
|
.await
|
||||||
.map(U256::from)
|
.map(U256::from)
|
||||||
} else if item == Self::GAS_LIMIT_VARIABLE {
|
} else if item == Self::GAS_LIMIT_VARIABLE {
|
||||||
@@ -799,7 +962,7 @@ mod tests {
|
|||||||
use alloy::{eips::BlockNumberOrTag, json_abi::JsonAbi};
|
use alloy::{eips::BlockNumberOrTag, json_abi::JsonAbi};
|
||||||
use alloy_primitives::{BlockHash, BlockNumber, BlockTimestamp, ChainId, TxHash, address};
|
use alloy_primitives::{BlockHash, BlockNumber, BlockTimestamp, ChainId, TxHash, address};
|
||||||
use alloy_sol_types::SolValue;
|
use alloy_sol_types::SolValue;
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, pin::Pin};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::metadata::ContractIdent;
|
use crate::metadata::ContractIdent;
|
||||||
@@ -807,40 +970,63 @@ mod tests {
|
|||||||
struct MockResolver;
|
struct MockResolver;
|
||||||
|
|
||||||
impl ResolverApi for MockResolver {
|
impl ResolverApi for MockResolver {
|
||||||
async fn chain_id(&self) -> anyhow::Result<ChainId> {
|
fn chain_id(&self) -> Pin<Box<dyn Future<Output = anyhow::Result<ChainId>> + '_>> {
|
||||||
Ok(0x123)
|
Box::pin(async move { Ok(0x123) })
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn block_gas_limit(&self, _: BlockNumberOrTag) -> anyhow::Result<u128> {
|
fn block_gas_limit(
|
||||||
Ok(0x1234)
|
&self,
|
||||||
|
_: BlockNumberOrTag,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<u128>> + '_>> {
|
||||||
|
Box::pin(async move { Ok(0x1234) })
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn block_coinbase(&self, _: BlockNumberOrTag) -> anyhow::Result<Address> {
|
fn block_coinbase(
|
||||||
Ok(Address::ZERO)
|
&self,
|
||||||
|
_: BlockNumberOrTag,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<Address>> + '_>> {
|
||||||
|
Box::pin(async move { Ok(Address::ZERO) })
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn block_difficulty(&self, _: BlockNumberOrTag) -> anyhow::Result<U256> {
|
fn block_difficulty(
|
||||||
Ok(U256::from(0x12345u128))
|
&self,
|
||||||
|
_: BlockNumberOrTag,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<U256>> + '_>> {
|
||||||
|
Box::pin(async move { Ok(U256::from(0x12345u128)) })
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn block_base_fee(&self, _: BlockNumberOrTag) -> anyhow::Result<u64> {
|
fn block_base_fee(
|
||||||
Ok(0x100)
|
&self,
|
||||||
|
_: BlockNumberOrTag,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<u64>> + '_>> {
|
||||||
|
Box::pin(async move { Ok(0x100) })
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn block_hash(&self, _: BlockNumberOrTag) -> anyhow::Result<BlockHash> {
|
fn block_hash(
|
||||||
Ok([0xEE; 32].into())
|
&self,
|
||||||
|
_: BlockNumberOrTag,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<BlockHash>> + '_>> {
|
||||||
|
Box::pin(async move { Ok([0xEE; 32].into()) })
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn block_timestamp(&self, _: BlockNumberOrTag) -> anyhow::Result<BlockTimestamp> {
|
fn block_timestamp(
|
||||||
Ok(0x123456)
|
&self,
|
||||||
|
_: BlockNumberOrTag,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<BlockTimestamp>> + '_>> {
|
||||||
|
Box::pin(async move { Ok(0x123456) })
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
fn last_block_number(
|
||||||
Ok(0x1234567)
|
&self,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<BlockNumber>> + '_>> {
|
||||||
|
Box::pin(async move { Ok(0x1234567) })
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn transaction_gas_price(&self, _: &TxHash) -> anyhow::Result<u128> {
|
fn transaction_gas_price(
|
||||||
Ok(0x200)
|
&self,
|
||||||
|
_: TxHash,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<u128>> + '_>> {
|
||||||
|
Box::pin(async move { Ok(0x200) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -867,7 +1053,7 @@ mod tests {
|
|||||||
.selector()
|
.selector()
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
let input = Input {
|
let input = FunctionCallStep {
|
||||||
instance: ContractInstance::new("Contract"),
|
instance: ContractInstance::new("Contract"),
|
||||||
method: Method::FunctionName("store".to_owned()),
|
method: Method::FunctionName("store".to_owned()),
|
||||||
calldata: Calldata::new_compound(["42"]),
|
calldata: Calldata::new_compound(["42"]),
|
||||||
@@ -911,7 +1097,7 @@ mod tests {
|
|||||||
.selector()
|
.selector()
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
let input: Input = Input {
|
let input: FunctionCallStep = FunctionCallStep {
|
||||||
instance: "Contract".to_owned().into(),
|
instance: "Contract".to_owned().into(),
|
||||||
method: Method::FunctionName("send(address)".to_owned()),
|
method: Method::FunctionName("send(address)".to_owned()),
|
||||||
calldata: Calldata::new_compound(["0x1000000000000000000000000000000000000001"]),
|
calldata: Calldata::new_compound(["0x1000000000000000000000000000000000000001"]),
|
||||||
@@ -958,7 +1144,7 @@ mod tests {
|
|||||||
.selector()
|
.selector()
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
let input: Input = Input {
|
let input: FunctionCallStep = FunctionCallStep {
|
||||||
instance: ContractInstance::new("Contract"),
|
instance: ContractInstance::new("Contract"),
|
||||||
method: Method::FunctionName("send".to_owned()),
|
method: Method::FunctionName("send".to_owned()),
|
||||||
calldata: Calldata::new_compound(["0x1000000000000000000000000000000000000001"]),
|
calldata: Calldata::new_compound(["0x1000000000000000000000000000000000000001"]),
|
||||||
@@ -987,7 +1173,7 @@ mod tests {
|
|||||||
async fn resolve_calldata_item(
|
async fn resolve_calldata_item(
|
||||||
input: &str,
|
input: &str,
|
||||||
deployed_contracts: &HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>,
|
deployed_contracts: &HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>,
|
||||||
resolver: &impl ResolverApi,
|
resolver: &(impl ResolverApi + ?Sized),
|
||||||
) -> anyhow::Result<U256> {
|
) -> anyhow::Result<U256> {
|
||||||
let context = ResolutionContext::default().with_deployed_contracts(deployed_contracts);
|
let context = ResolutionContext::default().with_deployed_contracts(deployed_contracts);
|
||||||
CalldataItem::new(input).resolve(resolver, context).await
|
CalldataItem::new(input).resolve(resolver, context).await
|
||||||
+29
-10
@@ -1,4 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
use alloy::eips::BlockNumberOrTag;
|
use alloy::eips::BlockNumberOrTag;
|
||||||
use alloy::json_abi::JsonAbi;
|
use alloy::json_abi::JsonAbi;
|
||||||
@@ -12,36 +13,54 @@ use crate::metadata::{ContractIdent, ContractInstance};
|
|||||||
/// crate implements to go from string calldata and into the bytes calldata.
|
/// crate implements to go from string calldata and into the bytes calldata.
|
||||||
pub trait ResolverApi {
|
pub trait ResolverApi {
|
||||||
/// Returns the ID of the chain that the node is on.
|
/// Returns the ID of the chain that the node is on.
|
||||||
fn chain_id(&self) -> impl Future<Output = Result<ChainId>>;
|
fn chain_id(&self) -> Pin<Box<dyn Future<Output = Result<ChainId>> + '_>>;
|
||||||
|
|
||||||
/// Returns the gas price for the specified transaction.
|
/// Returns the gas price for the specified transaction.
|
||||||
fn transaction_gas_price(&self, tx_hash: &TxHash) -> impl Future<Output = Result<u128>>;
|
fn transaction_gas_price(
|
||||||
|
&self,
|
||||||
|
tx_hash: TxHash,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<u128>> + '_>>;
|
||||||
|
|
||||||
// TODO: This is currently a u128 due to Kitchensink needing more than 64 bits for its gas limit
|
// TODO: This is currently a u128 due to substrate needing more than 64 bits for its gas limit
|
||||||
// when we implement the changes to the gas we need to adjust this to be a u64.
|
// when we implement the changes to the gas we need to adjust this to be a u64.
|
||||||
/// Returns the gas limit of the specified block.
|
/// Returns the gas limit of the specified block.
|
||||||
fn block_gas_limit(&self, number: BlockNumberOrTag) -> impl Future<Output = Result<u128>>;
|
fn block_gas_limit(
|
||||||
|
&self,
|
||||||
|
number: BlockNumberOrTag,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<u128>> + '_>>;
|
||||||
|
|
||||||
/// Returns the coinbase of the specified block.
|
/// Returns the coinbase of the specified block.
|
||||||
fn block_coinbase(&self, number: BlockNumberOrTag) -> impl Future<Output = Result<Address>>;
|
fn block_coinbase(
|
||||||
|
&self,
|
||||||
|
number: BlockNumberOrTag,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<Address>> + '_>>;
|
||||||
|
|
||||||
/// Returns the difficulty of the specified block.
|
/// Returns the difficulty of the specified block.
|
||||||
fn block_difficulty(&self, number: BlockNumberOrTag) -> impl Future<Output = Result<U256>>;
|
fn block_difficulty(
|
||||||
|
&self,
|
||||||
|
number: BlockNumberOrTag,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<U256>> + '_>>;
|
||||||
|
|
||||||
/// Returns the base fee of the specified block.
|
/// Returns the base fee of the specified block.
|
||||||
fn block_base_fee(&self, number: BlockNumberOrTag) -> impl Future<Output = Result<u64>>;
|
fn block_base_fee(
|
||||||
|
&self,
|
||||||
|
number: BlockNumberOrTag,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<u64>> + '_>>;
|
||||||
|
|
||||||
/// Returns the hash of the specified block.
|
/// Returns the hash of the specified block.
|
||||||
fn block_hash(&self, number: BlockNumberOrTag) -> impl Future<Output = Result<BlockHash>>;
|
fn block_hash(
|
||||||
|
&self,
|
||||||
|
number: BlockNumberOrTag,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<BlockHash>> + '_>>;
|
||||||
|
|
||||||
/// Returns the timestamp of the specified block,
|
/// Returns the timestamp of the specified block,
|
||||||
fn block_timestamp(
|
fn block_timestamp(
|
||||||
&self,
|
&self,
|
||||||
number: BlockNumberOrTag,
|
number: BlockNumberOrTag,
|
||||||
) -> impl Future<Output = Result<BlockTimestamp>>;
|
) -> Pin<Box<dyn Future<Output = Result<BlockTimestamp>> + '_>>;
|
||||||
|
|
||||||
/// Returns the number of the last block.
|
/// Returns the number of the last block.
|
||||||
fn last_block_number(&self) -> impl Future<Output = Result<BlockNumber>>;
|
fn last_block_number(&self) -> Pin<Box<dyn Future<Output = Result<BlockNumber>> + '_>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ repository.workspace = true
|
|||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
revive-common = { workspace = true }
|
||||||
|
|
||||||
|
revive-dt-format = { workspace = true }
|
||||||
|
|
||||||
alloy = { workspace = true }
|
alloy = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,53 @@
|
|||||||
//! This crate implements all node interactions.
|
//! This crate implements all node interactions.
|
||||||
|
|
||||||
use alloy::primitives::{Address, StorageKey, U256};
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use alloy::primitives::{Address, StorageKey, TxHash, U256};
|
||||||
use alloy::rpc::types::trace::geth::{DiffMode, GethDebugTracingOptions, GethTrace};
|
use alloy::rpc::types::trace::geth::{DiffMode, GethDebugTracingOptions, GethTrace};
|
||||||
use alloy::rpc::types::{EIP1186AccountProofResponse, TransactionReceipt, TransactionRequest};
|
use alloy::rpc::types::{EIP1186AccountProofResponse, TransactionReceipt, TransactionRequest};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use revive_common::EVMVersion;
|
||||||
|
use revive_dt_format::traits::ResolverApi;
|
||||||
|
|
||||||
/// An interface for all interactions with Ethereum compatible nodes.
|
/// An interface for all interactions with Ethereum compatible nodes.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
pub trait EthereumNode {
|
pub trait EthereumNode {
|
||||||
|
fn id(&self) -> usize;
|
||||||
|
|
||||||
|
/// Returns the nodes connection string.
|
||||||
|
fn connection_string(&self) -> &str;
|
||||||
|
|
||||||
/// Execute the [TransactionRequest] and return a [TransactionReceipt].
|
/// Execute the [TransactionRequest] and return a [TransactionReceipt].
|
||||||
fn execute_transaction(
|
fn execute_transaction(
|
||||||
&self,
|
&self,
|
||||||
transaction: TransactionRequest,
|
transaction: TransactionRequest,
|
||||||
) -> impl Future<Output = Result<TransactionReceipt>>;
|
) -> Pin<Box<dyn Future<Output = Result<TransactionReceipt>> + '_>>;
|
||||||
|
|
||||||
/// Trace the transaction in the [TransactionReceipt] and return a [GethTrace].
|
/// Trace the transaction in the [TransactionReceipt] and return a [GethTrace].
|
||||||
fn trace_transaction(
|
fn trace_transaction(
|
||||||
&self,
|
&self,
|
||||||
receipt: &TransactionReceipt,
|
tx_hash: TxHash,
|
||||||
trace_options: GethDebugTracingOptions,
|
trace_options: GethDebugTracingOptions,
|
||||||
) -> impl Future<Output = Result<GethTrace>>;
|
) -> Pin<Box<dyn Future<Output = Result<GethTrace>> + '_>>;
|
||||||
|
|
||||||
/// Returns the state diff of the transaction hash in the [TransactionReceipt].
|
/// Returns the state diff of the transaction hash in the [TransactionReceipt].
|
||||||
fn state_diff(&self, receipt: &TransactionReceipt) -> impl Future<Output = Result<DiffMode>>;
|
fn state_diff(&self, tx_hash: TxHash) -> Pin<Box<dyn Future<Output = Result<DiffMode>> + '_>>;
|
||||||
|
|
||||||
/// Returns the balance of the provided [`Address`] back.
|
/// Returns the balance of the provided [`Address`] back.
|
||||||
fn balance_of(&self, address: Address) -> impl Future<Output = Result<U256>>;
|
fn balance_of(&self, address: Address) -> Pin<Box<dyn Future<Output = Result<U256>> + '_>>;
|
||||||
|
|
||||||
/// Returns the latest storage proof of the provided [`Address`]
|
/// Returns the latest storage proof of the provided [`Address`]
|
||||||
fn latest_state_proof(
|
fn latest_state_proof(
|
||||||
&self,
|
&self,
|
||||||
address: Address,
|
address: Address,
|
||||||
keys: Vec<StorageKey>,
|
keys: Vec<StorageKey>,
|
||||||
) -> impl Future<Output = Result<EIP1186AccountProofResponse>>;
|
) -> Pin<Box<dyn Future<Output = Result<EIP1186AccountProofResponse>> + '_>>;
|
||||||
|
|
||||||
|
/// Returns the resolver that is to use with this ethereum node.
|
||||||
|
fn resolver(&self) -> Pin<Box<dyn Future<Output = Result<Arc<dyn ResolverApi + '_>>> + '_>>;
|
||||||
|
|
||||||
|
/// Returns the EVM version of the node.
|
||||||
|
fn evm_version(&self) -> EVMVersion;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ revive-dt-node-interaction = { workspace = true }
|
|||||||
|
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
serde_with = { workspace = true }
|
||||||
|
serde_yaml_ng = { workspace = true }
|
||||||
|
|
||||||
sp-core = { workspace = true }
|
sp-core = { workspace = true }
|
||||||
sp-runtime = { workspace = true }
|
sp-runtime = { workspace = true }
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/// This constant defines how much Wei accounts are pre-seeded with in genesis.
|
/// This constant defines how much Wei accounts are pre-seeded with in genesis.
|
||||||
///
|
///
|
||||||
/// Note: After changing this number, check that the tests for kitchensink work as we encountered
|
/// Note: After changing this number, check that the tests for substrate work as we encountered
|
||||||
/// some issues with different values of the initial balance on Kitchensink.
|
/// some issues with different values of the initial balance on substrate.
|
||||||
pub const INITIAL_BALANCE: u128 = 10u128.pow(37);
|
pub const INITIAL_BALANCE: u128 = 10u128.pow(37);
|
||||||
|
|||||||
+252
-254
@@ -1,16 +1,17 @@
|
|||||||
//! The go-ethereum node implementation.
|
//! The go-ethereum node implementation.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fs::{File, OpenOptions, create_dir_all, remove_dir_all},
|
fs::{File, create_dir_all, remove_dir_all},
|
||||||
io::{BufRead, BufReader, Read, Write},
|
io::Read,
|
||||||
ops::ControlFlow,
|
ops::ControlFlow,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::{Child, Command, Stdio},
|
pin::Pin,
|
||||||
|
process::{Command, Stdio},
|
||||||
sync::{
|
sync::{
|
||||||
Arc,
|
Arc,
|
||||||
atomic::{AtomicU32, Ordering},
|
atomic::{AtomicU32, Ordering},
|
||||||
},
|
},
|
||||||
time::{Duration, Instant},
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use alloy::{
|
use alloy::{
|
||||||
@@ -24,7 +25,7 @@ use alloy::{
|
|||||||
fillers::{CachedNonceManager, ChainIdFiller, FillProvider, NonceFiller, TxFiller},
|
fillers::{CachedNonceManager, ChainIdFiller, FillProvider, NonceFiller, TxFiller},
|
||||||
},
|
},
|
||||||
rpc::types::{
|
rpc::types::{
|
||||||
EIP1186AccountProofResponse, TransactionReceipt, TransactionRequest,
|
EIP1186AccountProofResponse, TransactionRequest,
|
||||||
trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame},
|
trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -40,7 +41,12 @@ use revive_dt_config::*;
|
|||||||
use revive_dt_format::traits::ResolverApi;
|
use revive_dt_format::traits::ResolverApi;
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
|
|
||||||
use crate::{Node, common::FallbackGasFiller, constants::INITIAL_BALANCE};
|
use crate::{
|
||||||
|
Node,
|
||||||
|
common::FallbackGasFiller,
|
||||||
|
constants::INITIAL_BALANCE,
|
||||||
|
process::{Process, ProcessReadinessWaitBehavior},
|
||||||
|
};
|
||||||
|
|
||||||
static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
|
static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
||||||
@@ -60,16 +66,11 @@ pub struct GethNode {
|
|||||||
logs_directory: PathBuf,
|
logs_directory: PathBuf,
|
||||||
geth: PathBuf,
|
geth: PathBuf,
|
||||||
id: u32,
|
id: u32,
|
||||||
handle: Option<Child>,
|
handle: Option<Process>,
|
||||||
start_timeout: Duration,
|
start_timeout: Duration,
|
||||||
wallet: Arc<EthereumWallet>,
|
wallet: Arc<EthereumWallet>,
|
||||||
nonce_manager: CachedNonceManager,
|
nonce_manager: CachedNonceManager,
|
||||||
chain_id_filler: ChainIdFiller,
|
chain_id_filler: ChainIdFiller,
|
||||||
/// 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
|
|
||||||
/// what it belongs to, we just want to flush them on [`Drop`] of the node.
|
|
||||||
logs_file_to_flush: Vec<File>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GethNode {
|
impl GethNode {
|
||||||
@@ -83,15 +84,46 @@ impl GethNode {
|
|||||||
const READY_MARKER: &str = "IPC endpoint opened";
|
const READY_MARKER: &str = "IPC endpoint opened";
|
||||||
const ERROR_MARKER: &str = "Fatal:";
|
const ERROR_MARKER: &str = "Fatal:";
|
||||||
|
|
||||||
const GETH_STDOUT_LOG_FILE_NAME: &str = "node_stdout.log";
|
|
||||||
const GETH_STDERR_LOG_FILE_NAME: &str = "node_stderr.log";
|
|
||||||
|
|
||||||
const TRANSACTION_INDEXING_ERROR: &str = "transaction indexing is in progress";
|
const TRANSACTION_INDEXING_ERROR: &str = "transaction indexing is in progress";
|
||||||
const TRANSACTION_TRACING_ERROR: &str = "historical state not available in path scheme yet";
|
const TRANSACTION_TRACING_ERROR: &str = "historical state not available in path scheme yet";
|
||||||
|
|
||||||
const RECEIPT_POLLING_DURATION: Duration = Duration::from_secs(5 * 60);
|
const RECEIPT_POLLING_DURATION: Duration = Duration::from_secs(5 * 60);
|
||||||
const TRACE_POLLING_DURATION: Duration = Duration::from_secs(60);
|
const TRACE_POLLING_DURATION: Duration = Duration::from_secs(60);
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
context: impl AsRef<WorkingDirectoryConfiguration>
|
||||||
|
+ AsRef<WalletConfiguration>
|
||||||
|
+ AsRef<GethConfiguration>
|
||||||
|
+ Clone,
|
||||||
|
) -> Self {
|
||||||
|
let working_directory_configuration =
|
||||||
|
AsRef::<WorkingDirectoryConfiguration>::as_ref(&context);
|
||||||
|
let wallet_configuration = AsRef::<WalletConfiguration>::as_ref(&context);
|
||||||
|
let geth_configuration = AsRef::<GethConfiguration>::as_ref(&context);
|
||||||
|
|
||||||
|
let geth_directory = working_directory_configuration
|
||||||
|
.as_path()
|
||||||
|
.join(Self::BASE_DIRECTORY);
|
||||||
|
let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||||
|
let base_directory = geth_directory.join(id.to_string());
|
||||||
|
|
||||||
|
let wallet = wallet_configuration.wallet();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
connection_string: base_directory.join(Self::IPC_FILE).display().to_string(),
|
||||||
|
data_directory: base_directory.join(Self::DATA_DIRECTORY),
|
||||||
|
logs_directory: base_directory.join(Self::LOGS_DIRECTORY),
|
||||||
|
base_directory,
|
||||||
|
geth: geth_configuration.path.clone(),
|
||||||
|
id,
|
||||||
|
handle: None,
|
||||||
|
start_timeout: geth_configuration.start_timeout_ms,
|
||||||
|
wallet: wallet.clone(),
|
||||||
|
chain_id_filler: Default::default(),
|
||||||
|
nonce_manager: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create the node directory and call `geth init` to configure the genesis.
|
/// Create the node directory and call `geth init` to configure the genesis.
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn init(&mut self, mut genesis: Genesis) -> anyhow::Result<&mut Self> {
|
fn init(&mut self, mut genesis: Genesis) -> anyhow::Result<&mut Self> {
|
||||||
@@ -156,25 +188,12 @@ impl GethNode {
|
|||||||
/// [Instance::init] must be called prior.
|
/// [Instance::init] must be called prior.
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn spawn_process(&mut self) -> anyhow::Result<&mut Self> {
|
fn spawn_process(&mut self) -> anyhow::Result<&mut Self> {
|
||||||
// This is the `OpenOptions` that we wish to use for all of the log files that we will be
|
let process = Process::new(
|
||||||
// opening in this method. We need to construct it in this way to:
|
None,
|
||||||
// 1. Be consistent
|
self.logs_directory.as_path(),
|
||||||
// 2. Less verbose and more dry
|
self.geth.as_path(),
|
||||||
// 3. Because the builder pattern uses mutable references so we need to get around that.
|
|command, stdout_file, stderr_file| {
|
||||||
let open_options = {
|
command
|
||||||
let mut options = OpenOptions::new();
|
|
||||||
options.create(true).truncate(true).write(true);
|
|
||||||
options
|
|
||||||
};
|
|
||||||
|
|
||||||
let stdout_logs_file = open_options
|
|
||||||
.clone()
|
|
||||||
.open(self.geth_stdout_log_file_path())
|
|
||||||
.context("Failed to open geth stdout logs file")?;
|
|
||||||
let stderr_logs_file = open_options
|
|
||||||
.open(self.geth_stderr_log_file_path())
|
|
||||||
.context("Failed to open geth stderr logs file")?;
|
|
||||||
self.handle = Command::new(&self.geth)
|
|
||||||
.arg("--dev")
|
.arg("--dev")
|
||||||
.arg("--datadir")
|
.arg("--datadir")
|
||||||
.arg(&self.data_directory)
|
.arg(&self.data_directory)
|
||||||
@@ -193,79 +212,37 @@ impl GethNode {
|
|||||||
.arg("full")
|
.arg("full")
|
||||||
.arg("--gcmode")
|
.arg("--gcmode")
|
||||||
.arg("archive")
|
.arg("archive")
|
||||||
.stderr(
|
.stderr(stderr_file)
|
||||||
stderr_logs_file
|
.stdout(stdout_file);
|
||||||
.try_clone()
|
},
|
||||||
.context("Failed to clone geth stderr log file handle")?,
|
ProcessReadinessWaitBehavior::TimeBoundedWaitFunction {
|
||||||
)
|
max_wait_duration: self.start_timeout,
|
||||||
.stdout(
|
check_function: Box::new(|_, stderr_line| match stderr_line {
|
||||||
stdout_logs_file
|
Some(line) => {
|
||||||
.try_clone()
|
|
||||||
.context("Failed to clone geth stdout log file handle")?,
|
|
||||||
)
|
|
||||||
.spawn()
|
|
||||||
.context("Failed to spawn geth node process")?
|
|
||||||
.into();
|
|
||||||
|
|
||||||
if let Err(error) = self.wait_ready() {
|
|
||||||
tracing::error!(?error, "Failed to start geth, shutting down gracefully");
|
|
||||||
self.shutdown()
|
|
||||||
.context("Failed to gracefully shutdown after geth start error")?;
|
|
||||||
return Err(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.logs_file_to_flush
|
|
||||||
.extend([stderr_logs_file, stdout_logs_file]);
|
|
||||||
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wait for the g-ethereum node child process getting ready.
|
|
||||||
///
|
|
||||||
/// [Instance::spawn_process] must be called priorly.
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
|
||||||
fn wait_ready(&mut self) -> anyhow::Result<&mut Self> {
|
|
||||||
let start_time = Instant::now();
|
|
||||||
|
|
||||||
let logs_file = OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(false)
|
|
||||||
.append(false)
|
|
||||||
.truncate(false)
|
|
||||||
.open(self.geth_stderr_log_file_path())
|
|
||||||
.context("Failed to open geth stderr logs file for readiness check")?;
|
|
||||||
|
|
||||||
let maximum_wait_time = self.start_timeout;
|
|
||||||
let mut stderr = BufReader::new(logs_file).lines();
|
|
||||||
let mut lines = vec![];
|
|
||||||
loop {
|
|
||||||
if let Some(Ok(line)) = stderr.next() {
|
|
||||||
if line.contains(Self::ERROR_MARKER) {
|
if line.contains(Self::ERROR_MARKER) {
|
||||||
anyhow::bail!("Failed to start geth {line}");
|
anyhow::bail!("Failed to start geth {line}");
|
||||||
|
} else if line.contains(Self::READY_MARKER) {
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
}
|
}
|
||||||
if line.contains(Self::READY_MARKER) {
|
|
||||||
return Ok(self);
|
|
||||||
}
|
}
|
||||||
lines.push(line);
|
None => Ok(false),
|
||||||
}
|
}),
|
||||||
if Instant::now().duration_since(start_time) > maximum_wait_time {
|
},
|
||||||
anyhow::bail!(
|
|
||||||
"Timeout in starting geth: took longer than {}ms. stdout:\n\n{}\n",
|
|
||||||
self.start_timeout.as_millis(),
|
|
||||||
lines.join("\n")
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
match process {
|
||||||
|
Ok(process) => self.handle = Some(process),
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!(?err, "Failed to start geth, shutting down gracefully");
|
||||||
|
self.shutdown()
|
||||||
|
.context("Failed to gracefully shutdown after geth start error")?;
|
||||||
|
return Err(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
Ok(self)
|
||||||
fn geth_stdout_log_file_path(&self) -> PathBuf {
|
|
||||||
self.logs_directory.join(Self::GETH_STDOUT_LOG_FILE_NAME)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
|
||||||
fn geth_stderr_log_file_path(&self) -> PathBuf {
|
|
||||||
self.logs_directory.join(Self::GETH_STDERR_LOG_FILE_NAME)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn provider(
|
async fn provider(
|
||||||
@@ -289,16 +266,26 @@ impl GethNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EthereumNode for GethNode {
|
impl EthereumNode for GethNode {
|
||||||
|
fn id(&self) -> usize {
|
||||||
|
self.id as _
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connection_string(&self) -> &str {
|
||||||
|
&self.connection_string
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(
|
#[instrument(
|
||||||
level = "info",
|
level = "info",
|
||||||
skip_all,
|
skip_all,
|
||||||
fields(geth_node_id = self.id, connection_string = self.connection_string),
|
fields(geth_node_id = self.id, connection_string = self.connection_string),
|
||||||
err,
|
err,
|
||||||
)]
|
)]
|
||||||
async fn execute_transaction(
|
fn execute_transaction(
|
||||||
&self,
|
&self,
|
||||||
transaction: TransactionRequest,
|
transaction: TransactionRequest,
|
||||||
) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> {
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<alloy::rpc::types::TransactionReceipt>> + '_>>
|
||||||
|
{
|
||||||
|
Box::pin(async move {
|
||||||
let provider = self
|
let provider = self
|
||||||
.provider()
|
.provider()
|
||||||
.await
|
.await
|
||||||
@@ -356,14 +343,17 @@ impl EthereumNode for GethNode {
|
|||||||
?transaction_hash
|
?transaction_hash
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn trace_transaction(
|
fn trace_transaction(
|
||||||
&self,
|
&self,
|
||||||
transaction: &TransactionReceipt,
|
tx_hash: TxHash,
|
||||||
trace_options: GethDebugTracingOptions,
|
trace_options: GethDebugTracingOptions,
|
||||||
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<alloy::rpc::types::trace::geth::GethTrace>> + '_>>
|
||||||
|
{
|
||||||
|
Box::pin(async move {
|
||||||
let provider = Arc::new(
|
let provider = Arc::new(
|
||||||
self.provider()
|
self.provider()
|
||||||
.await
|
.await
|
||||||
@@ -377,7 +367,7 @@ impl EthereumNode for GethNode {
|
|||||||
let trace_options = trace_options.clone();
|
let trace_options = trace_options.clone();
|
||||||
async move {
|
async move {
|
||||||
match provider
|
match provider
|
||||||
.debug_trace_transaction(transaction.transaction_hash, trace_options)
|
.debug_trace_transaction(tx_hash, trace_options)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(trace) => Ok(ControlFlow::Break(trace)),
|
Ok(trace) => Ok(ControlFlow::Break(trace)),
|
||||||
@@ -393,17 +383,22 @@ impl EthereumNode for GethNode {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result<DiffMode> {
|
fn state_diff(
|
||||||
|
&self,
|
||||||
|
tx_hash: TxHash,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<DiffMode>> + '_>> {
|
||||||
|
Box::pin(async move {
|
||||||
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
||||||
diff_mode: Some(true),
|
diff_mode: Some(true),
|
||||||
disable_code: None,
|
disable_code: None,
|
||||||
disable_storage: None,
|
disable_storage: None,
|
||||||
});
|
});
|
||||||
match self
|
match self
|
||||||
.trace_transaction(transaction, trace_options)
|
.trace_transaction(tx_hash, trace_options)
|
||||||
.await
|
.await
|
||||||
.context("Failed to trace transaction for prestate diff")?
|
.context("Failed to trace transaction for prestate diff")?
|
||||||
.try_into_pre_state_frame()
|
.try_into_pre_state_frame()
|
||||||
@@ -412,24 +407,31 @@ impl EthereumNode for GethNode {
|
|||||||
PreStateFrame::Diff(diff) => Ok(diff),
|
PreStateFrame::Diff(diff) => Ok(diff),
|
||||||
_ => anyhow::bail!("expected a diff mode trace"),
|
_ => anyhow::bail!("expected a diff mode trace"),
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn balance_of(&self, address: Address) -> anyhow::Result<U256> {
|
fn balance_of(
|
||||||
|
&self,
|
||||||
|
address: Address,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<U256>> + '_>> {
|
||||||
|
Box::pin(async move {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get the Geth provider")?
|
.context("Failed to get the Geth provider")?
|
||||||
.get_balance(address)
|
.get_balance(address)
|
||||||
.await
|
.await
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn latest_state_proof(
|
fn latest_state_proof(
|
||||||
&self,
|
&self,
|
||||||
address: Address,
|
address: Address,
|
||||||
keys: Vec<StorageKey>,
|
keys: Vec<StorageKey>,
|
||||||
) -> anyhow::Result<EIP1186AccountProofResponse> {
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<EIP1186AccountProofResponse>> + '_>> {
|
||||||
|
Box::pin(async move {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get the Geth provider")?
|
.context("Failed to get the Geth provider")?
|
||||||
@@ -437,72 +439,104 @@ impl EthereumNode for GethNode {
|
|||||||
.latest()
|
.latest()
|
||||||
.await
|
.await
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn resolver(
|
||||||
|
&self,
|
||||||
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<Arc<dyn ResolverApi + '_>>> + '_>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let id = self.id;
|
||||||
|
let provider = self.provider().await?;
|
||||||
|
Ok(Arc::new(GethNodeResolver { id, provider }) as Arc<dyn ResolverApi>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evm_version(&self) -> EVMVersion {
|
||||||
|
EVMVersion::Cancun
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResolverApi for GethNode {
|
pub struct GethNodeResolver<F: TxFiller<Ethereum>, P: Provider<Ethereum>> {
|
||||||
|
id: u32,
|
||||||
|
provider: FillProvider<F, P, Ethereum>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: TxFiller<Ethereum>, P: Provider<Ethereum>> ResolverApi for GethNodeResolver<F, P> {
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
|
fn chain_id(
|
||||||
self.provider()
|
&self,
|
||||||
.await
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<alloy::primitives::ChainId>> + '_>> {
|
||||||
.context("Failed to get the Geth provider")?
|
Box::pin(async move { self.provider.get_chain_id().await.map_err(Into::into) })
|
||||||
.get_chain_id()
|
|
||||||
.await
|
|
||||||
.map_err(Into::into)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result<u128> {
|
fn transaction_gas_price(
|
||||||
self.provider()
|
&self,
|
||||||
.await
|
tx_hash: TxHash,
|
||||||
.context("Failed to get the Geth provider")?
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<u128>> + '_>> {
|
||||||
.get_transaction_receipt(*tx_hash)
|
Box::pin(async move {
|
||||||
|
self.provider
|
||||||
|
.get_transaction_receipt(tx_hash)
|
||||||
.await?
|
.await?
|
||||||
.context("Failed to get the transaction receipt")
|
.context("Failed to get the transaction receipt")
|
||||||
.map(|receipt| receipt.effective_gas_price)
|
.map(|receipt| receipt.effective_gas_price)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result<u128> {
|
fn block_gas_limit(
|
||||||
self.provider()
|
&self,
|
||||||
.await
|
number: BlockNumberOrTag,
|
||||||
.context("Failed to get the Geth provider")?
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<u128>> + '_>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
self.provider
|
||||||
.get_block_by_number(number)
|
.get_block_by_number(number)
|
||||||
.await
|
.await
|
||||||
.context("Failed to get the geth block")?
|
.context("Failed to get the geth block")?
|
||||||
.context("Failed to get the Geth block, perhaps there are no blocks?")
|
.context("Failed to get the Geth block, perhaps there are no blocks?")
|
||||||
.map(|block| block.header.gas_limit as _)
|
.map(|block| block.header.gas_limit as _)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result<Address> {
|
fn block_coinbase(
|
||||||
self.provider()
|
&self,
|
||||||
.await
|
number: BlockNumberOrTag,
|
||||||
.context("Failed to get the Geth provider")?
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<Address>> + '_>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
self.provider
|
||||||
.get_block_by_number(number)
|
.get_block_by_number(number)
|
||||||
.await
|
.await
|
||||||
.context("Failed to get the geth block")?
|
.context("Failed to get the geth block")?
|
||||||
.context("Failed to get the Geth block, perhaps there are no blocks?")
|
.context("Failed to get the Geth block, perhaps there are no blocks?")
|
||||||
.map(|block| block.header.beneficiary)
|
.map(|block| block.header.beneficiary)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result<U256> {
|
fn block_difficulty(
|
||||||
self.provider()
|
&self,
|
||||||
.await
|
number: BlockNumberOrTag,
|
||||||
.context("Failed to get the Geth provider")?
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<U256>> + '_>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
self.provider
|
||||||
.get_block_by_number(number)
|
.get_block_by_number(number)
|
||||||
.await
|
.await
|
||||||
.context("Failed to get the geth block")?
|
.context("Failed to get the geth block")?
|
||||||
.context("Failed to get the Geth block, perhaps there are no blocks?")
|
.context("Failed to get the Geth block, perhaps there are no blocks?")
|
||||||
.map(|block| U256::from_be_bytes(block.header.mix_hash.0))
|
.map(|block| U256::from_be_bytes(block.header.mix_hash.0))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result<u64> {
|
fn block_base_fee(
|
||||||
self.provider()
|
&self,
|
||||||
.await
|
number: BlockNumberOrTag,
|
||||||
.context("Failed to get the Geth provider")?
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<u64>> + '_>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
self.provider
|
||||||
.get_block_by_number(number)
|
.get_block_by_number(number)
|
||||||
.await
|
.await
|
||||||
.context("Failed to get the geth block")?
|
.context("Failed to get the geth block")?
|
||||||
@@ -513,109 +547,49 @@ impl ResolverApi for GethNode {
|
|||||||
.base_fee_per_gas
|
.base_fee_per_gas
|
||||||
.context("Failed to get the base fee per gas")
|
.context("Failed to get the base fee per gas")
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn block_hash(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockHash> {
|
fn block_hash(
|
||||||
self.provider()
|
&self,
|
||||||
.await
|
number: BlockNumberOrTag,
|
||||||
.context("Failed to get the Geth provider")?
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<BlockHash>> + '_>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
self.provider
|
||||||
.get_block_by_number(number)
|
.get_block_by_number(number)
|
||||||
.await
|
.await
|
||||||
.context("Failed to get the geth block")?
|
.context("Failed to get the geth block")?
|
||||||
.context("Failed to get the Geth block, perhaps there are no blocks?")
|
.context("Failed to get the Geth block, perhaps there are no blocks?")
|
||||||
.map(|block| block.header.hash)
|
.map(|block| block.header.hash)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn block_timestamp(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockTimestamp> {
|
fn block_timestamp(
|
||||||
self.provider()
|
&self,
|
||||||
.await
|
number: BlockNumberOrTag,
|
||||||
.context("Failed to get the Geth provider")?
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<BlockTimestamp>> + '_>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
self.provider
|
||||||
.get_block_by_number(number)
|
.get_block_by_number(number)
|
||||||
.await
|
.await
|
||||||
.context("Failed to get the geth block")?
|
.context("Failed to get the geth block")?
|
||||||
.context("Failed to get the Geth block, perhaps there are no blocks?")
|
.context("Failed to get the Geth block, perhaps there are no blocks?")
|
||||||
.map(|block| block.header.timestamp)
|
.map(|block| block.header.timestamp)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
fn last_block_number(&self) -> Pin<Box<dyn Future<Output = anyhow::Result<BlockNumber>> + '_>> {
|
||||||
self.provider()
|
Box::pin(async move { self.provider.get_block_number().await.map_err(Into::into) })
|
||||||
.await
|
|
||||||
.context("Failed to get the Geth provider")?
|
|
||||||
.get_block_number()
|
|
||||||
.await
|
|
||||||
.map_err(Into::into)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node for GethNode {
|
impl Node for GethNode {
|
||||||
fn new(
|
|
||||||
context: impl AsRef<WorkingDirectoryConfiguration>
|
|
||||||
+ AsRef<ConcurrencyConfiguration>
|
|
||||||
+ AsRef<GenesisConfiguration>
|
|
||||||
+ AsRef<WalletConfiguration>
|
|
||||||
+ AsRef<GethConfiguration>
|
|
||||||
+ AsRef<KitchensinkConfiguration>
|
|
||||||
+ AsRef<ReviveDevNodeConfiguration>
|
|
||||||
+ AsRef<EthRpcConfiguration>
|
|
||||||
+ Clone,
|
|
||||||
) -> Self {
|
|
||||||
let working_directory_configuration =
|
|
||||||
AsRef::<WorkingDirectoryConfiguration>::as_ref(&context);
|
|
||||||
let wallet_configuration = AsRef::<WalletConfiguration>::as_ref(&context);
|
|
||||||
let geth_configuration = AsRef::<GethConfiguration>::as_ref(&context);
|
|
||||||
|
|
||||||
let geth_directory = working_directory_configuration
|
|
||||||
.as_path()
|
|
||||||
.join(Self::BASE_DIRECTORY);
|
|
||||||
let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst);
|
|
||||||
let base_directory = geth_directory.join(id.to_string());
|
|
||||||
|
|
||||||
let wallet = wallet_configuration.wallet();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
connection_string: base_directory.join(Self::IPC_FILE).display().to_string(),
|
|
||||||
data_directory: base_directory.join(Self::DATA_DIRECTORY),
|
|
||||||
logs_directory: base_directory.join(Self::LOGS_DIRECTORY),
|
|
||||||
base_directory,
|
|
||||||
geth: geth_configuration.path.clone(),
|
|
||||||
id,
|
|
||||||
handle: None,
|
|
||||||
start_timeout: geth_configuration.start_timeout_ms,
|
|
||||||
wallet: wallet.clone(),
|
|
||||||
chain_id_filler: Default::default(),
|
|
||||||
nonce_manager: Default::default(),
|
|
||||||
// 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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
|
||||||
fn id(&self) -> usize {
|
|
||||||
self.id as _
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
|
||||||
fn connection_string(&self) -> String {
|
|
||||||
self.connection_string.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn shutdown(&mut self) -> anyhow::Result<()> {
|
fn shutdown(&mut self) -> anyhow::Result<()> {
|
||||||
// Terminate the processes in a graceful manner to allow for the output to be flushed.
|
drop(self.handle.take());
|
||||||
if let Some(mut child) = self.handle.take() {
|
|
||||||
child
|
|
||||||
.kill()
|
|
||||||
.map_err(|error| anyhow::anyhow!("Failed to kill the geth process: {error:?}"))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flushing the files that we're using for keeping the logs before shutdown.
|
|
||||||
for file in self.logs_file_to_flush.iter_mut() {
|
|
||||||
file.flush()?
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the node's database so that subsequent runs do not run on the same database. We
|
// Remove the node's database so that subsequent runs do not run on the same database. We
|
||||||
// ignore the error just in case the directory didn't exist in the first place and therefore
|
// ignore the error just in case the directory didn't exist in the first place and therefore
|
||||||
@@ -645,17 +619,6 @@ impl Node for GethNode {
|
|||||||
.stdout;
|
.stdout;
|
||||||
Ok(String::from_utf8_lossy(&output).into())
|
Ok(String::from_utf8_lossy(&output).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches_target(targets: Option<&[String]>) -> bool {
|
|
||||||
match targets {
|
|
||||||
None => true,
|
|
||||||
Some(targets) => targets.iter().any(|str| str.as_str() == "evm"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn evm_version() -> EVMVersion {
|
|
||||||
EVMVersion::Cancun
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for GethNode {
|
impl Drop for GethNode {
|
||||||
@@ -667,13 +630,15 @@ impl Drop for GethNode {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn test_config() -> ExecutionContext {
|
fn test_config() -> TestExecutionContext {
|
||||||
ExecutionContext::default()
|
TestExecutionContext::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_node() -> (ExecutionContext, GethNode) {
|
fn new_node() -> (TestExecutionContext, GethNode) {
|
||||||
let context = test_config();
|
let context = test_config();
|
||||||
let mut node = GethNode::new(&context);
|
let mut node = GethNode::new(&context);
|
||||||
node.init(context.genesis_configuration.genesis().unwrap().clone())
|
node.init(context.genesis_configuration.genesis().unwrap().clone())
|
||||||
@@ -683,9 +648,21 @@ mod tests {
|
|||||||
(context, node)
|
(context, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn shared_node() -> &'static GethNode {
|
||||||
|
static NODE: LazyLock<(TestExecutionContext, GethNode)> = LazyLock::new(new_node);
|
||||||
|
&NODE.1
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn version_works() {
|
fn version_works() {
|
||||||
let version = GethNode::new(&test_config()).version().unwrap();
|
// Arrange
|
||||||
|
let node = shared_node();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let version = node.version();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let version = version.expect("Failed to get the version");
|
||||||
assert!(
|
assert!(
|
||||||
version.starts_with("geth version"),
|
version.starts_with("geth version"),
|
||||||
"expected version string, got: '{version}'"
|
"expected version string, got: '{version}'"
|
||||||
@@ -695,10 +672,10 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn can_get_chain_id_from_node() {
|
async fn can_get_chain_id_from_node() {
|
||||||
// Arrange
|
// Arrange
|
||||||
let (_context, node) = new_node();
|
let node = shared_node();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let chain_id = node.chain_id().await;
|
let chain_id = node.resolver().await.unwrap().chain_id().await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
let chain_id = chain_id.expect("Failed to get the chain id");
|
let chain_id = chain_id.expect("Failed to get the chain id");
|
||||||
@@ -708,49 +685,66 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn can_get_gas_limit_from_node() {
|
async fn can_get_gas_limit_from_node() {
|
||||||
// Arrange
|
// Arrange
|
||||||
let (_context, node) = new_node();
|
let node = shared_node();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let gas_limit = node.block_gas_limit(BlockNumberOrTag::Latest).await;
|
let gas_limit = node
|
||||||
|
.resolver()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.block_gas_limit(BlockNumberOrTag::Latest)
|
||||||
|
.await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
let gas_limit = gas_limit.expect("Failed to get the gas limit");
|
let _ = gas_limit.expect("Failed to get the gas limit");
|
||||||
assert_eq!(gas_limit, u32::MAX as u128)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn can_get_coinbase_from_node() {
|
async fn can_get_coinbase_from_node() {
|
||||||
// Arrange
|
// Arrange
|
||||||
let (_context, node) = new_node();
|
let node = shared_node();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let coinbase = node.block_coinbase(BlockNumberOrTag::Latest).await;
|
let coinbase = node
|
||||||
|
.resolver()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.block_coinbase(BlockNumberOrTag::Latest)
|
||||||
|
.await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
let coinbase = coinbase.expect("Failed to get the coinbase");
|
let _ = coinbase.expect("Failed to get the coinbase");
|
||||||
assert_eq!(coinbase, Address::new([0xFF; 20]))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn can_get_block_difficulty_from_node() {
|
async fn can_get_block_difficulty_from_node() {
|
||||||
// Arrange
|
// Arrange
|
||||||
let (_context, node) = new_node();
|
let node = shared_node();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let block_difficulty = node.block_difficulty(BlockNumberOrTag::Latest).await;
|
let block_difficulty = node
|
||||||
|
.resolver()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.block_difficulty(BlockNumberOrTag::Latest)
|
||||||
|
.await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
let block_difficulty = block_difficulty.expect("Failed to get the block difficulty");
|
let _ = block_difficulty.expect("Failed to get the block difficulty");
|
||||||
assert_eq!(block_difficulty, U256::ZERO)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn can_get_block_hash_from_node() {
|
async fn can_get_block_hash_from_node() {
|
||||||
// Arrange
|
// Arrange
|
||||||
let (_context, node) = new_node();
|
let node = shared_node();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let block_hash = node.block_hash(BlockNumberOrTag::Latest).await;
|
let block_hash = node
|
||||||
|
.resolver()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.block_hash(BlockNumberOrTag::Latest)
|
||||||
|
.await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
let _ = block_hash.expect("Failed to get the block hash");
|
let _ = block_hash.expect("Failed to get the block hash");
|
||||||
@@ -759,10 +753,15 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn can_get_block_timestamp_from_node() {
|
async fn can_get_block_timestamp_from_node() {
|
||||||
// Arrange
|
// Arrange
|
||||||
let (_context, node) = new_node();
|
let node = shared_node();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let block_timestamp = node.block_timestamp(BlockNumberOrTag::Latest).await;
|
let block_timestamp = node
|
||||||
|
.resolver()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.block_timestamp(BlockNumberOrTag::Latest)
|
||||||
|
.await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
let _ = block_timestamp.expect("Failed to get the block timestamp");
|
let _ = block_timestamp.expect("Failed to get the block timestamp");
|
||||||
@@ -771,13 +770,12 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn can_get_block_number_from_node() {
|
async fn can_get_block_number_from_node() {
|
||||||
// Arrange
|
// Arrange
|
||||||
let (_context, node) = new_node();
|
let node = shared_node();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let block_number = node.last_block_number().await;
|
let block_number = node.resolver().await.unwrap().last_block_number().await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
let block_number = block_number.expect("Failed to get the block number");
|
let _ = block_number.expect("Failed to get the block number");
|
||||||
assert_eq!(block_number, 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-30
@@ -1,34 +1,17 @@
|
|||||||
//! This crate implements the testing nodes.
|
//! This crate implements the testing nodes.
|
||||||
|
|
||||||
use alloy::genesis::Genesis;
|
use alloy::genesis::Genesis;
|
||||||
use revive_common::EVMVersion;
|
|
||||||
use revive_dt_config::*;
|
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
|
|
||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod geth;
|
pub mod geth;
|
||||||
pub mod kitchensink;
|
pub mod lighthouse_geth;
|
||||||
pub mod pool;
|
pub mod process;
|
||||||
|
pub mod substrate;
|
||||||
|
|
||||||
/// An abstract interface for testing nodes.
|
/// An abstract interface for testing nodes.
|
||||||
pub trait Node: EthereumNode {
|
pub trait Node: EthereumNode {
|
||||||
/// Create a new uninitialized instance.
|
|
||||||
fn new(
|
|
||||||
context: impl AsRef<WorkingDirectoryConfiguration>
|
|
||||||
+ AsRef<ConcurrencyConfiguration>
|
|
||||||
+ AsRef<GenesisConfiguration>
|
|
||||||
+ AsRef<WalletConfiguration>
|
|
||||||
+ AsRef<GethConfiguration>
|
|
||||||
+ AsRef<KitchensinkConfiguration>
|
|
||||||
+ AsRef<ReviveDevNodeConfiguration>
|
|
||||||
+ AsRef<EthRpcConfiguration>
|
|
||||||
+ Clone,
|
|
||||||
) -> Self;
|
|
||||||
|
|
||||||
/// Returns the identifier of the node.
|
|
||||||
fn id(&self) -> usize;
|
|
||||||
|
|
||||||
/// Spawns a node configured according to the genesis json.
|
/// Spawns a node configured according to the genesis json.
|
||||||
///
|
///
|
||||||
/// Blocking until it's ready to accept transactions.
|
/// Blocking until it's ready to accept transactions.
|
||||||
@@ -39,16 +22,6 @@ pub trait Node: EthereumNode {
|
|||||||
/// Blocking until it's completely stopped.
|
/// Blocking until it's completely stopped.
|
||||||
fn shutdown(&mut self) -> anyhow::Result<()>;
|
fn shutdown(&mut self) -> anyhow::Result<()>;
|
||||||
|
|
||||||
/// Returns the nodes connection string.
|
|
||||||
fn connection_string(&self) -> String;
|
|
||||||
|
|
||||||
/// Returns the node version.
|
/// Returns the node version.
|
||||||
fn version(&self) -> anyhow::Result<String>;
|
fn version(&self) -> anyhow::Result<String>;
|
||||||
|
|
||||||
/// Given a list of targets from the metadata file, this function determines if the metadata
|
|
||||||
/// file can be ran on this node or not.
|
|
||||||
fn matches_target(targets: Option<&[String]>) -> bool;
|
|
||||||
|
|
||||||
/// Returns the EVM version of the node.
|
|
||||||
fn evm_version() -> EVMVersion;
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,110 +0,0 @@
|
|||||||
//! This crate implements concurrent handling of testing node.
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
sync::atomic::{AtomicUsize, Ordering},
|
|
||||||
thread,
|
|
||||||
};
|
|
||||||
|
|
||||||
use alloy::genesis::Genesis;
|
|
||||||
use anyhow::Context as _;
|
|
||||||
use revive_dt_config::{
|
|
||||||
ConcurrencyConfiguration, EthRpcConfiguration, GenesisConfiguration, GethConfiguration,
|
|
||||||
KitchensinkConfiguration, ReviveDevNodeConfiguration, WalletConfiguration,
|
|
||||||
WorkingDirectoryConfiguration,
|
|
||||||
};
|
|
||||||
use tracing::info;
|
|
||||||
|
|
||||||
use crate::Node;
|
|
||||||
|
|
||||||
/// The node pool starts one or more [Node] which then can be accessed
|
|
||||||
/// in a round robbin fasion.
|
|
||||||
pub struct NodePool<T> {
|
|
||||||
next: AtomicUsize,
|
|
||||||
nodes: Vec<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> NodePool<T>
|
|
||||||
where
|
|
||||||
T: Node + Send + 'static,
|
|
||||||
{
|
|
||||||
/// Create a new Pool. This will start as many nodes as there are workers in `config`.
|
|
||||||
pub fn new(
|
|
||||||
context: impl AsRef<WorkingDirectoryConfiguration>
|
|
||||||
+ AsRef<ConcurrencyConfiguration>
|
|
||||||
+ AsRef<GenesisConfiguration>
|
|
||||||
+ AsRef<WalletConfiguration>
|
|
||||||
+ AsRef<GethConfiguration>
|
|
||||||
+ AsRef<KitchensinkConfiguration>
|
|
||||||
+ AsRef<ReviveDevNodeConfiguration>
|
|
||||||
+ AsRef<EthRpcConfiguration>
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ Clone
|
|
||||||
+ 'static,
|
|
||||||
) -> anyhow::Result<Self> {
|
|
||||||
let concurrency_configuration = AsRef::<ConcurrencyConfiguration>::as_ref(&context);
|
|
||||||
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
|
|
||||||
|
|
||||||
let nodes = concurrency_configuration.number_of_nodes;
|
|
||||||
let genesis = genesis_configuration.genesis()?;
|
|
||||||
|
|
||||||
let mut handles = Vec::with_capacity(nodes);
|
|
||||||
for _ in 0..nodes {
|
|
||||||
let context = context.clone();
|
|
||||||
let genesis = genesis.clone();
|
|
||||||
handles.push(thread::spawn(move || spawn_node::<T>(context, genesis)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut nodes = Vec::with_capacity(nodes);
|
|
||||||
for handle in handles {
|
|
||||||
nodes.push(
|
|
||||||
handle
|
|
||||||
.join()
|
|
||||||
.map_err(|error| anyhow::anyhow!("failed to spawn node: {:?}", error))
|
|
||||||
.context("Failed to join node spawn thread")?
|
|
||||||
.map_err(|error| anyhow::anyhow!("node failed to spawn: {error}"))
|
|
||||||
.context("Node failed to spawn")?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
nodes,
|
|
||||||
next: Default::default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a handle to the next node.
|
|
||||||
pub fn round_robbin(&self) -> &T {
|
|
||||||
let current = self.next.fetch_add(1, Ordering::SeqCst) % self.nodes.len();
|
|
||||||
self.nodes.get(current).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_node<T: Node + Send>(
|
|
||||||
context: impl AsRef<WorkingDirectoryConfiguration>
|
|
||||||
+ AsRef<ConcurrencyConfiguration>
|
|
||||||
+ AsRef<GenesisConfiguration>
|
|
||||||
+ AsRef<WalletConfiguration>
|
|
||||||
+ AsRef<GethConfiguration>
|
|
||||||
+ AsRef<KitchensinkConfiguration>
|
|
||||||
+ AsRef<ReviveDevNodeConfiguration>
|
|
||||||
+ AsRef<EthRpcConfiguration>
|
|
||||||
+ Clone
|
|
||||||
+ 'static,
|
|
||||||
genesis: Genesis,
|
|
||||||
) -> anyhow::Result<T> {
|
|
||||||
let mut node = T::new(context);
|
|
||||||
info!(
|
|
||||||
id = node.id(),
|
|
||||||
connection_string = node.connection_string(),
|
|
||||||
"Spawning node"
|
|
||||||
);
|
|
||||||
node.spawn(genesis)
|
|
||||||
.context("Failed to spawn node process")?;
|
|
||||||
info!(
|
|
||||||
id = node.id(),
|
|
||||||
connection_string = node.connection_string(),
|
|
||||||
"Spawned node"
|
|
||||||
);
|
|
||||||
Ok(node)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
use std::{
|
||||||
|
fs::{File, OpenOptions},
|
||||||
|
io::{BufRead, BufReader, Write},
|
||||||
|
path::Path,
|
||||||
|
process::{Child, Command},
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result, bail};
|
||||||
|
|
||||||
|
/// A wrapper around processes which allows for their stdout and stderr to be logged and flushed
|
||||||
|
/// when the process is dropped.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Process {
|
||||||
|
/// The handle of the child process.
|
||||||
|
child: Child,
|
||||||
|
|
||||||
|
/// The file that stdout is being logged to.
|
||||||
|
stdout_logs_file: File,
|
||||||
|
|
||||||
|
/// The file that stderr is being logged to.
|
||||||
|
stderr_logs_file: File,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Process {
|
||||||
|
pub fn new(
|
||||||
|
log_file_prefix: impl Into<Option<&'static str>>,
|
||||||
|
logs_directory: impl AsRef<Path>,
|
||||||
|
binary_path: impl AsRef<Path>,
|
||||||
|
command_building_callback: impl FnOnce(&mut Command, File, File),
|
||||||
|
process_readiness_wait_behavior: ProcessReadinessWaitBehavior,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let log_file_prefix = log_file_prefix.into();
|
||||||
|
|
||||||
|
let (stdout_file_name, stderr_file_name) = match log_file_prefix {
|
||||||
|
Some(prefix) => (
|
||||||
|
format!("{prefix}_stdout.log"),
|
||||||
|
format!("{prefix}_stderr.log"),
|
||||||
|
),
|
||||||
|
None => ("stdout.log".to_string(), "stderr.log".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let stdout_logs_file_path = logs_directory.as_ref().join(stdout_file_name);
|
||||||
|
let stderr_logs_file_path = logs_directory.as_ref().join(stderr_file_name);
|
||||||
|
|
||||||
|
let stdout_logs_file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.truncate(true)
|
||||||
|
.create(true)
|
||||||
|
.open(stdout_logs_file_path.as_path())
|
||||||
|
.context("Failed to open the stdout logs file")?;
|
||||||
|
let stderr_logs_file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.truncate(true)
|
||||||
|
.create(true)
|
||||||
|
.open(stderr_logs_file_path.as_path())
|
||||||
|
.context("Failed to open the stderr logs file")?;
|
||||||
|
|
||||||
|
let mut command = {
|
||||||
|
let stdout_logs_file = stdout_logs_file
|
||||||
|
.try_clone()
|
||||||
|
.context("Failed to clone the stdout logs file")?;
|
||||||
|
let stderr_logs_file = stderr_logs_file
|
||||||
|
.try_clone()
|
||||||
|
.context("Failed to clone the stderr logs file")?;
|
||||||
|
|
||||||
|
let mut command = Command::new(binary_path.as_ref());
|
||||||
|
command_building_callback(&mut command, stdout_logs_file, stderr_logs_file);
|
||||||
|
command
|
||||||
|
};
|
||||||
|
let mut child = command
|
||||||
|
.spawn()
|
||||||
|
.context("Failed to spawn the built command")?;
|
||||||
|
|
||||||
|
match process_readiness_wait_behavior {
|
||||||
|
ProcessReadinessWaitBehavior::NoStartupWait => {}
|
||||||
|
ProcessReadinessWaitBehavior::WaitDuration(duration) => std::thread::sleep(duration),
|
||||||
|
ProcessReadinessWaitBehavior::TimeBoundedWaitFunction {
|
||||||
|
max_wait_duration,
|
||||||
|
mut check_function,
|
||||||
|
} => {
|
||||||
|
let spawn_time = Instant::now();
|
||||||
|
|
||||||
|
let stdout_logs_file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.open(stdout_logs_file_path)
|
||||||
|
.context("Failed to open the stdout logs file")?;
|
||||||
|
let stderr_logs_file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.open(stderr_logs_file_path)
|
||||||
|
.context("Failed to open the stderr logs file")?;
|
||||||
|
|
||||||
|
let mut stdout_lines = BufReader::new(stdout_logs_file).lines();
|
||||||
|
let mut stderr_lines = BufReader::new(stderr_logs_file).lines();
|
||||||
|
|
||||||
|
let mut stdout = String::new();
|
||||||
|
let mut stderr = String::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let stdout_line = stdout_lines.next().and_then(Result::ok);
|
||||||
|
let stderr_line = stderr_lines.next().and_then(Result::ok);
|
||||||
|
|
||||||
|
if let Some(stdout_line) = stdout_line.as_ref() {
|
||||||
|
stdout.push_str(stdout_line);
|
||||||
|
stdout.push('\n');
|
||||||
|
}
|
||||||
|
if let Some(stderr_line) = stderr_line.as_ref() {
|
||||||
|
stderr.push_str(stderr_line);
|
||||||
|
stderr.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
let check_result =
|
||||||
|
check_function(stdout_line.as_deref(), stderr_line.as_deref())
|
||||||
|
.context("Failed to wait for the process to be ready")?;
|
||||||
|
|
||||||
|
if check_result {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if Instant::now().duration_since(spawn_time) > max_wait_duration {
|
||||||
|
bail!(
|
||||||
|
"Waited for the process to start but it failed to start in time. stderr {stderr} - stdout {stdout}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ProcessReadinessWaitBehavior::WaitForCommandToExit => {
|
||||||
|
if !child
|
||||||
|
.wait()
|
||||||
|
.context("Failed waiting for kurtosis run process to finish")?
|
||||||
|
.success()
|
||||||
|
{
|
||||||
|
anyhow::bail!("Failed to initialize kurtosis network",);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
child,
|
||||||
|
stdout_logs_file,
|
||||||
|
stderr_logs_file,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Process {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.child.kill().expect("Failed to kill the process");
|
||||||
|
self.stdout_logs_file
|
||||||
|
.flush()
|
||||||
|
.expect("Failed to flush the stdout logs file");
|
||||||
|
self.stderr_logs_file
|
||||||
|
.flush()
|
||||||
|
.expect("Failed to flush the stderr logs file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ProcessReadinessWaitBehavior {
|
||||||
|
/// The process does not require any kind of wait after it's been spawned and can be used
|
||||||
|
/// straight away.
|
||||||
|
NoStartupWait,
|
||||||
|
|
||||||
|
/// Waits for the command to exit.
|
||||||
|
WaitForCommandToExit,
|
||||||
|
|
||||||
|
/// The process does require some amount of wait duration after it's been started.
|
||||||
|
WaitDuration(Duration),
|
||||||
|
|
||||||
|
/// The process requires a time bounded wait function which is a function of the lines that
|
||||||
|
/// appear in the log files.
|
||||||
|
TimeBoundedWaitFunction {
|
||||||
|
/// The maximum amount of time to wait for the check function to return true.
|
||||||
|
max_wait_duration: Duration,
|
||||||
|
|
||||||
|
/// The function to use to check if the process spawned is ready to use or not. This
|
||||||
|
/// function should return the following in the following cases:
|
||||||
|
///
|
||||||
|
/// - `Ok(true)`: Returned when the condition the process is waiting for has been fulfilled
|
||||||
|
/// and the wait is completed.
|
||||||
|
/// - `Ok(false)`: The process is not ready yet but it might be ready in the future.
|
||||||
|
/// - `Err`: The process is not ready yet and will not be ready in the future as it appears
|
||||||
|
/// that it has encountered an error when it was being spawned.
|
||||||
|
///
|
||||||
|
/// The first argument is a line from stdout and the second argument is a line from stderr.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
check_function: Box<dyn FnMut(Option<&str>, Option<&str>) -> anyhow::Result<bool>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,9 @@ use std::{
|
|||||||
use alloy_primitives::Address;
|
use alloy_primitives::Address;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
use revive_dt_common::types::PlatformIdentifier;
|
||||||
use revive_dt_compiler::{CompilerInput, CompilerOutput, Mode};
|
use revive_dt_compiler::{CompilerInput, CompilerOutput, Mode};
|
||||||
use revive_dt_config::{Context, TestingPlatform};
|
use revive_dt_config::Context;
|
||||||
use revive_dt_format::{case::CaseIdx, corpus::Corpus, metadata::ContractInstance};
|
use revive_dt_format::{case::CaseIdx, corpus::Corpus, metadata::ContractInstance};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -84,11 +85,8 @@ impl ReportAggregator {
|
|||||||
RunnerEvent::TestIgnored(event) => {
|
RunnerEvent::TestIgnored(event) => {
|
||||||
self.handle_test_ignored_event(*event);
|
self.handle_test_ignored_event(*event);
|
||||||
}
|
}
|
||||||
RunnerEvent::LeaderNodeAssigned(event) => {
|
RunnerEvent::NodeAssigned(event) => {
|
||||||
self.handle_leader_node_assigned_event(*event);
|
self.handle_node_assigned_event(*event);
|
||||||
}
|
|
||||||
RunnerEvent::FollowerNodeAssigned(event) => {
|
|
||||||
self.handle_follower_node_assigned_event(*event);
|
|
||||||
}
|
}
|
||||||
RunnerEvent::PreLinkContractsCompilationSucceeded(event) => {
|
RunnerEvent::PreLinkContractsCompilationSucceeded(event) => {
|
||||||
self.handle_pre_link_contracts_compilation_succeeded_event(*event)
|
self.handle_pre_link_contracts_compilation_succeeded_event(*event)
|
||||||
@@ -257,28 +255,15 @@ impl ReportAggregator {
|
|||||||
let _ = self.listener_tx.send(event);
|
let _ = self.listener_tx.send(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_leader_node_assigned_event(&mut self, event: LeaderNodeAssignedEvent) {
|
fn handle_node_assigned_event(&mut self, event: NodeAssignedEvent) {
|
||||||
let execution_information = self.execution_information(&ExecutionSpecifier {
|
let execution_information = self.execution_information(&ExecutionSpecifier {
|
||||||
test_specifier: event.test_specifier,
|
test_specifier: event.test_specifier,
|
||||||
node_id: event.id,
|
node_id: event.id,
|
||||||
node_designation: NodeDesignation::Leader,
|
platform_identifier: event.platform_identifier,
|
||||||
});
|
});
|
||||||
execution_information.node = Some(TestCaseNodeInformation {
|
execution_information.node = Some(TestCaseNodeInformation {
|
||||||
id: event.id,
|
id: event.id,
|
||||||
platform: event.platform,
|
platform_identifier: event.platform_identifier,
|
||||||
connection_string: event.connection_string,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_follower_node_assigned_event(&mut self, event: FollowerNodeAssignedEvent) {
|
|
||||||
let execution_information = self.execution_information(&ExecutionSpecifier {
|
|
||||||
test_specifier: event.test_specifier,
|
|
||||||
node_id: event.id,
|
|
||||||
node_designation: NodeDesignation::Follower,
|
|
||||||
});
|
|
||||||
execution_information.node = Some(TestCaseNodeInformation {
|
|
||||||
id: event.id,
|
|
||||||
platform: event.platform,
|
|
||||||
connection_string: event.connection_string,
|
connection_string: event.connection_string,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -413,14 +398,11 @@ impl ReportAggregator {
|
|||||||
specifier: &ExecutionSpecifier,
|
specifier: &ExecutionSpecifier,
|
||||||
) -> &mut ExecutionInformation {
|
) -> &mut ExecutionInformation {
|
||||||
let test_case_report = self.test_case_report(&specifier.test_specifier);
|
let test_case_report = self.test_case_report(&specifier.test_specifier);
|
||||||
match specifier.node_designation {
|
test_case_report
|
||||||
NodeDesignation::Leader => test_case_report
|
.platform_execution
|
||||||
.leader_execution_information
|
.entry(specifier.platform_identifier)
|
||||||
.get_or_insert_default(),
|
.or_default()
|
||||||
NodeDesignation::Follower => test_case_report
|
.get_or_insert_default()
|
||||||
.follower_execution_information
|
|
||||||
.get_or_insert_default(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,12 +437,8 @@ pub struct TestCaseReport {
|
|||||||
/// Information on the status of the test case and whether it succeeded, failed, or was ignored.
|
/// Information on the status of the test case and whether it succeeded, failed, or was ignored.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub status: Option<TestCaseStatus>,
|
pub status: Option<TestCaseStatus>,
|
||||||
/// Information related to the execution on the leader.
|
/// Information related to the execution on one of the platforms.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
pub platform_execution: BTreeMap<PlatformIdentifier, Option<ExecutionInformation>>,
|
||||||
pub leader_execution_information: Option<ExecutionInformation>,
|
|
||||||
/// Information related to the execution on the follower.
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub follower_execution_information: Option<ExecutionInformation>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information related to the status of the test. Could be that the test succeeded, failed, or that
|
/// Information related to the status of the test. Could be that the test succeeded, failed, or that
|
||||||
@@ -488,18 +466,18 @@ pub enum TestCaseStatus {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information related to the leader or follower node that's being used to execute the step.
|
/// Information related to the platform node that's being used to execute the step.
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
pub struct TestCaseNodeInformation {
|
pub struct TestCaseNodeInformation {
|
||||||
/// The ID of the node that this case is being executed on.
|
/// The ID of the node that this case is being executed on.
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
/// The platform of the node.
|
/// The platform of the node.
|
||||||
pub platform: TestingPlatform,
|
pub platform_identifier: PlatformIdentifier,
|
||||||
/// The connection string of the node.
|
/// The connection string of the node.
|
||||||
pub connection_string: String,
|
pub connection_string: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execution information tied to the leader or the follower.
|
/// Execution information tied to the platform.
|
||||||
#[derive(Clone, Debug, Default, Serialize)]
|
#[derive(Clone, Debug, Default, Serialize)]
|
||||||
pub struct ExecutionInformation {
|
pub struct ExecutionInformation {
|
||||||
/// Information related to the node assigned to this test case.
|
/// Information related to the node assigned to this test case.
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
use std::{path::PathBuf, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use revive_dt_common::define_wrapper_type;
|
use revive_dt_common::{define_wrapper_type, types::PlatformIdentifier};
|
||||||
use revive_dt_compiler::Mode;
|
use revive_dt_compiler::Mode;
|
||||||
use revive_dt_format::{case::CaseIdx, input::StepIdx};
|
use revive_dt_format::{case::CaseIdx, steps::StepPath};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
define_wrapper_type!(
|
define_wrapper_type!(
|
||||||
@@ -22,22 +22,16 @@ pub struct TestSpecifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An absolute path for a test that also includes information about the node that it's assigned to
|
/// An absolute path for a test that also includes information about the node that it's assigned to
|
||||||
/// and whether it's the leader or follower.
|
/// and what platform it belongs to.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct ExecutionSpecifier {
|
pub struct ExecutionSpecifier {
|
||||||
pub test_specifier: Arc<TestSpecifier>,
|
pub test_specifier: Arc<TestSpecifier>,
|
||||||
pub node_id: usize,
|
pub node_id: usize,
|
||||||
pub node_designation: NodeDesignation,
|
pub platform_identifier: PlatformIdentifier,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub enum NodeDesignation {
|
|
||||||
Leader,
|
|
||||||
Follower,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct StepExecutionSpecifier {
|
pub struct StepExecutionSpecifier {
|
||||||
pub execution_specifier: Arc<ExecutionSpecifier>,
|
pub execution_specifier: Arc<ExecutionSpecifier>,
|
||||||
pub step_idx: StepIdx,
|
pub step_idx: StepPath,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use std::{collections::BTreeMap, path::PathBuf, sync::Arc};
|
|||||||
use alloy_primitives::Address;
|
use alloy_primitives::Address;
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
use revive_dt_common::types::PlatformIdentifier;
|
||||||
use revive_dt_compiler::{CompilerInput, CompilerOutput};
|
use revive_dt_compiler::{CompilerInput, CompilerOutput};
|
||||||
use revive_dt_config::TestingPlatform;
|
|
||||||
use revive_dt_format::metadata::Metadata;
|
use revive_dt_format::metadata::Metadata;
|
||||||
use revive_dt_format::{corpus::Corpus, metadata::ContractInstance};
|
use revive_dt_format::{corpus::Corpus, metadata::ContractInstance};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
@@ -412,14 +412,14 @@ macro_rules! define_event {
|
|||||||
pub fn execution_specific_reporter(
|
pub fn execution_specific_reporter(
|
||||||
&self,
|
&self,
|
||||||
node_id: impl Into<usize>,
|
node_id: impl Into<usize>,
|
||||||
node_designation: impl Into<$crate::common::NodeDesignation>
|
platform_identifier: impl Into<PlatformIdentifier>
|
||||||
) -> [< $ident ExecutionSpecificReporter >] {
|
) -> [< $ident ExecutionSpecificReporter >] {
|
||||||
[< $ident ExecutionSpecificReporter >] {
|
[< $ident ExecutionSpecificReporter >] {
|
||||||
reporter: self.reporter.clone(),
|
reporter: self.reporter.clone(),
|
||||||
execution_specifier: Arc::new($crate::common::ExecutionSpecifier {
|
execution_specifier: Arc::new($crate::common::ExecutionSpecifier {
|
||||||
test_specifier: self.test_specifier.clone(),
|
test_specifier: self.test_specifier.clone(),
|
||||||
node_id: node_id.into(),
|
node_id: node_id.into(),
|
||||||
node_designation: node_designation.into(),
|
platform_identifier: platform_identifier.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -434,7 +434,7 @@ macro_rules! define_event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A reporter that's tied to a specific execution of the test case such as execution on
|
/// A reporter that's tied to a specific execution of the test case such as execution on
|
||||||
/// a specific node like the leader or follower.
|
/// a specific node from a specific platform.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct [< $ident ExecutionSpecificReporter >] {
|
pub struct [< $ident ExecutionSpecificReporter >] {
|
||||||
$vis reporter: [< $ident Reporter >],
|
$vis reporter: [< $ident Reporter >],
|
||||||
@@ -520,25 +520,14 @@ define_event! {
|
|||||||
/// A reason for the failure of the test.
|
/// A reason for the failure of the test.
|
||||||
reason: String,
|
reason: String,
|
||||||
},
|
},
|
||||||
/// An event emitted when the test case is assigned a leader node.
|
/// An event emitted when the test case is assigned a platform node.
|
||||||
LeaderNodeAssigned {
|
NodeAssigned {
|
||||||
/// A specifier for the test that the assignment is for.
|
/// A specifier for the test that the assignment is for.
|
||||||
test_specifier: Arc<TestSpecifier>,
|
test_specifier: Arc<TestSpecifier>,
|
||||||
/// The ID of the node that this case is being executed on.
|
/// The ID of the node that this case is being executed on.
|
||||||
id: usize,
|
id: usize,
|
||||||
/// The platform of the node.
|
/// The identifier of the platform used.
|
||||||
platform: TestingPlatform,
|
platform_identifier: PlatformIdentifier,
|
||||||
/// The connection string of the node.
|
|
||||||
connection_string: String,
|
|
||||||
},
|
|
||||||
/// An event emitted when the test case is assigned a follower node.
|
|
||||||
FollowerNodeAssigned {
|
|
||||||
/// A specifier for the test that the assignment is for.
|
|
||||||
test_specifier: Arc<TestSpecifier>,
|
|
||||||
/// The ID of the node that this case is being executed on.
|
|
||||||
id: usize,
|
|
||||||
/// The platform of the node.
|
|
||||||
platform: TestingPlatform,
|
|
||||||
/// The connection string of the node.
|
/// The connection string of the node.
|
||||||
connection_string: String,
|
connection_string: String,
|
||||||
},
|
},
|
||||||
|
|||||||
+3
-1
@@ -89,7 +89,9 @@ echo "This may take a while..."
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Run the tool
|
# Run the tool
|
||||||
RUST_LOG="error" cargo run --release -- execute-tests \
|
RUST_LOG="info" cargo run --release -- execute-tests \
|
||||||
|
--platform geth-evm-solc \
|
||||||
|
--platform revive-dev-node-polkavm-resolc \
|
||||||
--corpus "$CORPUS_FILE" \
|
--corpus "$CORPUS_FILE" \
|
||||||
--working-directory "$WORKDIR" \
|
--working-directory "$WORKDIR" \
|
||||||
--concurrency.number-of-nodes 5 \
|
--concurrency.number-of-nodes 5 \
|
||||||
|
|||||||
+98
-12
@@ -25,7 +25,7 @@
|
|||||||
"null"
|
"null"
|
||||||
],
|
],
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"$ref": "#/$defs/VmIdentifier"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cases": {
|
"cases": {
|
||||||
@@ -95,6 +95,26 @@
|
|||||||
"cases"
|
"cases"
|
||||||
],
|
],
|
||||||
"$defs": {
|
"$defs": {
|
||||||
|
"VmIdentifier": {
|
||||||
|
"description": "An enum representing the identifiers of the supported VMs.",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"description": "The ethereum virtual machine.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "evm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "The EraVM virtual machine.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "eravm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Polkadot's PolaVM Risc-v based virtual machine.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "polkavm"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"Case": {
|
"Case": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -168,19 +188,27 @@
|
|||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"description": "A function call or an invocation to some function on some smart contract.",
|
"description": "A function call or an invocation to some function on some smart contract.",
|
||||||
"$ref": "#/$defs/Input"
|
"$ref": "#/$defs/FunctionCallStep"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "A step for performing a balance assertion on some account or contract.",
|
"description": "A step for performing a balance assertion on some account or contract.",
|
||||||
"$ref": "#/$defs/BalanceAssertion"
|
"$ref": "#/$defs/BalanceAssertionStep"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "A step for asserting that the storage of some contract or account is empty.",
|
"description": "A step for asserting that the storage of some contract or account is empty.",
|
||||||
"$ref": "#/$defs/StorageEmptyAssertion"
|
"$ref": "#/$defs/StorageEmptyAssertionStep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "A special step for repeating a bunch of steps a certain number of times.",
|
||||||
|
"$ref": "#/$defs/RepeatStep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "A step type that allows for a new account address to be allocated and to later on be used\nas the caller in another step.",
|
||||||
|
"$ref": "#/$defs/AllocateAccountStep"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Input": {
|
"FunctionCallStep": {
|
||||||
"description": "This is an input step which is a transaction description that the framework translates into a\ntransaction and executes on the nodes.",
|
"description": "This is an input step which is a transaction description that the framework translates into a\ntransaction and executes on the nodes.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -353,9 +381,13 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"address": {
|
"address": {
|
||||||
"description": "An optional field of the address of the emitter of the event.",
|
"description": "An optional field of the address of the emitter of the event.",
|
||||||
"type": [
|
"anyOf": [
|
||||||
"string",
|
{
|
||||||
"null"
|
"$ref": "#/$defs/StepAddress"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"topics": {
|
"topics": {
|
||||||
@@ -375,6 +407,10 @@
|
|||||||
"values"
|
"values"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"StepAddress": {
|
||||||
|
"description": "An address type that might either be an address literal or a resolvable address.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"EtherValue": {
|
"EtherValue": {
|
||||||
"description": "Defines an Ether value.\n\nThis is an unsigned 256 bit integer that's followed by some denomination which can either be\neth, ether, gwei, or wei.",
|
"description": "Defines an Ether value.\n\nThis is an unsigned 256 bit integer that's followed by some denomination which can either be\neth, ether, gwei, or wei.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -394,7 +430,7 @@
|
|||||||
"return_data"
|
"return_data"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"BalanceAssertion": {
|
"BalanceAssertionStep": {
|
||||||
"description": "This represents a balance assertion step where the framework needs to query the balance of some\naccount or contract and assert that it's some amount.",
|
"description": "This represents a balance assertion step where the framework needs to query the balance of some\naccount or contract and assert that it's some amount.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -407,7 +443,7 @@
|
|||||||
},
|
},
|
||||||
"address": {
|
"address": {
|
||||||
"description": "The address that the balance assertion should be done on.\n\nThis is a string which will be resolved into an address when being processed. Therefore,\nthis could be a normal hex address, a variable such as `Test.address`, or perhaps even a\nfull on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are\nfollowed in the calldata.",
|
"description": "The address that the balance assertion should be done on.\n\nThis is a string which will be resolved into an address when being processed. Therefore,\nthis could be a normal hex address, a variable such as `Test.address`, or perhaps even a\nfull on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are\nfollowed in the calldata.",
|
||||||
"type": "string"
|
"$ref": "#/$defs/StepAddress"
|
||||||
},
|
},
|
||||||
"expected_balance": {
|
"expected_balance": {
|
||||||
"description": "The amount of balance to assert that the account or contract has. This is a 256 bit string\nthat's serialized and deserialized into a decimal string.",
|
"description": "The amount of balance to assert that the account or contract has. This is a 256 bit string\nthat's serialized and deserialized into a decimal string.",
|
||||||
@@ -419,7 +455,8 @@
|
|||||||
"expected_balance"
|
"expected_balance"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"StorageEmptyAssertion": {
|
"StorageEmptyAssertionStep": {
|
||||||
|
"description": "This represents an assertion for the storage of some contract or account and whether it's empty\nor not.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"comment": {
|
"comment": {
|
||||||
@@ -431,7 +468,7 @@
|
|||||||
},
|
},
|
||||||
"address": {
|
"address": {
|
||||||
"description": "The address that the balance assertion should be done on.\n\nThis is a string which will be resolved into an address when being processed. Therefore,\nthis could be a normal hex address, a variable such as `Test.address`, or perhaps even a\nfull on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are\nfollowed in the calldata.",
|
"description": "The address that the balance assertion should be done on.\n\nThis is a string which will be resolved into an address when being processed. Therefore,\nthis could be a normal hex address, a variable such as `Test.address`, or perhaps even a\nfull on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are\nfollowed in the calldata.",
|
||||||
"type": "string"
|
"$ref": "#/$defs/StepAddress"
|
||||||
},
|
},
|
||||||
"is_storage_empty": {
|
"is_storage_empty": {
|
||||||
"description": "A boolean of whether the storage of the address is empty or not.",
|
"description": "A boolean of whether the storage of the address is empty or not.",
|
||||||
@@ -443,6 +480,55 @@
|
|||||||
"is_storage_empty"
|
"is_storage_empty"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"RepeatStep": {
|
||||||
|
"description": "This represents a repetition step which is a special step type that allows for a sequence of\nsteps to be repeated (on different drivers) a certain number of times.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"comment": {
|
||||||
|
"description": "An optional comment on the repetition step.",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"repeat": {
|
||||||
|
"description": "The number of repetitions that the steps should be repeated for.",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"description": "The sequence of steps to repeat for the above defined number of repetitions.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/Step"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"repeat",
|
||||||
|
"steps"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"AllocateAccountStep": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"comment": {
|
||||||
|
"description": "An optional comment on the account allocation step.",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"allocate_account": {
|
||||||
|
"description": "An instruction to allocate a new account with the value being the variable name of that\naccount. This must start with `$VARIABLE:` and then be followed by the variable name of the\naccount.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"allocate_account"
|
||||||
|
]
|
||||||
|
},
|
||||||
"ContractPathAndIdent": {
|
"ContractPathAndIdent": {
|
||||||
"description": "Represents an identifier used for contracts.\n\nThe type supports serialization from and into the following string format:\n\n```text\n${path}:${contract_ident}\n```",
|
"description": "Represents an identifier used for contracts.\n\nThe type supports serialization from and into the following string format:\n\n```text\n${path}:${contract_ident}\n```",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|||||||
Reference in New Issue
Block a user