mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-04-22 21:57:58 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 91773dd281 | |||
| c34f5a3d95 | |||
| 19075f76e0 | |||
| 0038634695 | |||
| 09d56f5177 | |||
| a59e287fa1 | |||
| f2045db0e9 | |||
| 5a11f44673 | |||
| 46aea0890d | |||
| 9b40c9b9e3 | |||
| f67a9bf643 | |||
| 67d767ffde | |||
| f7fbe094ec | |||
| 90b2dd4cfe | |||
| 64d63ef999 |
Generated
+594
-8
@@ -659,7 +659,7 @@ checksum = "4f317d20f047b3de4d9728c556e2e9a92c9a507702d2016424cd8be13a74ca5e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-json-rpc",
|
"alloy-json-rpc",
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"derive_more 2.0.1",
|
"derive_more 2.0.1",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-utils-wasm",
|
"futures-utils-wasm",
|
||||||
@@ -1189,6 +1189,151 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-channel"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"event-listener 2.5.3",
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-channel"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"event-listener-strategy",
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-executor"
|
||||||
|
version = "1.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa"
|
||||||
|
dependencies = [
|
||||||
|
"async-task",
|
||||||
|
"concurrent-queue",
|
||||||
|
"fastrand",
|
||||||
|
"futures-lite",
|
||||||
|
"pin-project-lite",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-global-executor"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel 2.5.0",
|
||||||
|
"async-executor",
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"blocking",
|
||||||
|
"futures-lite",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-io"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3"
|
||||||
|
dependencies = [
|
||||||
|
"async-lock",
|
||||||
|
"cfg-if",
|
||||||
|
"concurrent-queue",
|
||||||
|
"futures-io",
|
||||||
|
"futures-lite",
|
||||||
|
"parking",
|
||||||
|
"polling",
|
||||||
|
"rustix",
|
||||||
|
"slab",
|
||||||
|
"tracing",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-lock"
|
||||||
|
version = "3.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener 5.4.1",
|
||||||
|
"event-listener-strategy",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-process"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel 2.5.0",
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"async-signal",
|
||||||
|
"async-task",
|
||||||
|
"blocking",
|
||||||
|
"cfg-if",
|
||||||
|
"event-listener 5.4.1",
|
||||||
|
"futures-lite",
|
||||||
|
"rustix",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-signal"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d"
|
||||||
|
dependencies = [
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"atomic-waker",
|
||||||
|
"cfg-if",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"rustix",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"slab",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-std"
|
||||||
|
version = "1.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel 1.9.0",
|
||||||
|
"async-global-executor",
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"async-process",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-lite",
|
||||||
|
"gloo-timers",
|
||||||
|
"kv-log-macro",
|
||||||
|
"log",
|
||||||
|
"memchr",
|
||||||
|
"once_cell",
|
||||||
|
"pin-project-lite",
|
||||||
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-stream"
|
name = "async-stream"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
@@ -1211,6 +1356,12 @@ dependencies = [
|
|||||||
"syn 2.0.101",
|
"syn 2.0.101",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-task"
|
||||||
|
version = "4.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.88"
|
version = "0.1.88"
|
||||||
@@ -1266,6 +1417,12 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.21.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
@@ -1404,6 +1561,19 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blocking"
|
||||||
|
version = "1.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel 2.5.0",
|
||||||
|
"async-task",
|
||||||
|
"futures-io",
|
||||||
|
"futures-lite",
|
||||||
|
"piper",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blst"
|
name = "blst"
|
||||||
version = "0.3.14"
|
version = "0.3.14"
|
||||||
@@ -1437,6 +1607,29 @@ dependencies = [
|
|||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bson"
|
||||||
|
version = "2.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7969a9ba84b0ff843813e7249eed1678d9b6607ce5a3b8f0a47af3fcf7978e6e"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"bitvec",
|
||||||
|
"getrandom 0.2.16",
|
||||||
|
"getrandom 0.3.3",
|
||||||
|
"hex",
|
||||||
|
"indexmap 2.10.0",
|
||||||
|
"js-sys",
|
||||||
|
"once_cell",
|
||||||
|
"rand 0.9.2",
|
||||||
|
"serde",
|
||||||
|
"serde_bytes",
|
||||||
|
"serde_json",
|
||||||
|
"time",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.17.0"
|
version = "3.17.0"
|
||||||
@@ -1479,6 +1672,32 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cacache"
|
||||||
|
version = "13.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c5063741c7b2e260bbede781cf4679632dd90e2718e99f7715e46824b65670b"
|
||||||
|
dependencies = [
|
||||||
|
"async-std",
|
||||||
|
"digest 0.10.7",
|
||||||
|
"either",
|
||||||
|
"futures",
|
||||||
|
"hex",
|
||||||
|
"libc",
|
||||||
|
"memmap2",
|
||||||
|
"miette",
|
||||||
|
"reflink-copy",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"sha1",
|
||||||
|
"sha2 0.10.9",
|
||||||
|
"ssri",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.25"
|
version = "1.2.25"
|
||||||
@@ -1559,6 +1778,15 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101"
|
checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "concurrent-queue"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-hex"
|
name = "const-hex"
|
||||||
version = "1.14.1"
|
version = "1.14.1"
|
||||||
@@ -1644,6 +1872,15 @@ version = "2.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-channel"
|
||||||
|
version = "0.5.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-deque"
|
name = "crossbeam-deque"
|
||||||
version = "0.8.6"
|
version = "0.8.6"
|
||||||
@@ -2111,6 +2348,33 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener"
|
||||||
|
version = "2.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener"
|
||||||
|
version = "5.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"parking",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener-strategy"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener 5.4.1",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "expander"
|
name = "expander"
|
||||||
version = "2.2.1"
|
version = "2.2.1"
|
||||||
@@ -2352,6 +2616,19 @@ version = "0.3.31"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-lite"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"parking",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
@@ -2399,6 +2676,20 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9"
|
checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generator"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"rustversion",
|
||||||
|
"windows",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.7"
|
version = "0.14.7"
|
||||||
@@ -2417,8 +2708,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2428,9 +2721,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi",
|
"r-efi",
|
||||||
"wasi 0.14.2+wasi-0.2.4",
|
"wasi 0.14.2+wasi-0.2.4",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2455,6 +2750,18 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gloo-timers"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "group"
|
name = "group"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@@ -2549,6 +2856,12 @@ version = "0.3.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex"
|
name = "hex"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -2701,7 +3014,7 @@ version = "0.1.13"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8"
|
checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -3071,6 +3384,15 @@ dependencies = [
|
|||||||
"sha3-asm",
|
"sha3-asm",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kv-log-macro"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -3096,7 +3418,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e79019718125edc905a079a70cfa5f3820bc76139fc91d6f9abc27ea2a887139"
|
checksum = "e79019718125edc905a079a70cfa5f3820bc76139fc91d6f9abc27ea2a887139"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayref",
|
"arrayref",
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"digest 0.9.0",
|
"digest 0.9.0",
|
||||||
"hmac-drbg",
|
"hmac-drbg",
|
||||||
"libsecp256k1-core",
|
"libsecp256k1-core",
|
||||||
@@ -3164,6 +3486,22 @@ name = "log"
|
|||||||
version = "0.4.27"
|
version = "0.4.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
dependencies = [
|
||||||
|
"value-bag",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "loom"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"generator",
|
||||||
|
"scoped-tls",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru"
|
name = "lru"
|
||||||
@@ -3200,6 +3538,15 @@ version = "2.7.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memmap2"
|
||||||
|
version = "0.5.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memory-db"
|
name = "memory-db"
|
||||||
version = "0.32.0"
|
version = "0.32.0"
|
||||||
@@ -3221,6 +3568,29 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miette"
|
||||||
|
version = "5.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e"
|
||||||
|
dependencies = [
|
||||||
|
"miette-derive",
|
||||||
|
"once_cell",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miette-derive"
|
||||||
|
version = "5.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.101",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mime"
|
name = "mime"
|
||||||
version = "0.3.17"
|
version = "0.3.17"
|
||||||
@@ -3247,6 +3617,25 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "moka"
|
||||||
|
version = "0.12.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"loom",
|
||||||
|
"parking_lot",
|
||||||
|
"portable-atomic",
|
||||||
|
"rustc_version 0.4.1",
|
||||||
|
"smallvec",
|
||||||
|
"tagptr",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
@@ -3331,7 +3720,7 @@ version = "1.16.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi 0.3.9",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3488,6 +3877,12 @@ dependencies = [
|
|||||||
"syn 2.0.101",
|
"syn 2.0.101",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.4"
|
version = "0.12.4"
|
||||||
@@ -3593,6 +3988,17 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "piper"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-waker",
|
||||||
|
"fastrand",
|
||||||
|
"futures-io",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkcs8"
|
name = "pkcs8"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
@@ -3646,6 +4052,27 @@ dependencies = [
|
|||||||
"syn 2.0.101",
|
"syn 2.0.101",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "polling"
|
||||||
|
version = "3.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"concurrent-queue",
|
||||||
|
"hermit-abi 0.5.2",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustix",
|
||||||
|
"tracing",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "potential_utf"
|
name = "potential_utf"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -3927,6 +4354,18 @@ dependencies = [
|
|||||||
"syn 2.0.101",
|
"syn 2.0.101",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reflink-copy"
|
||||||
|
version = "0.1.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78c81d000a2c524133cc00d2f92f019d399e57906c3b7119271a2495354fe895"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"rustix",
|
||||||
|
"windows",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@@ -3977,7 +4416,7 @@ version = "0.12.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e98ff6b0dbbe4d5a37318f433d4fc82babd21631f194d370409ceb2e40b2f0b5"
|
checksum = "e98ff6b0dbbe4d5a37318f433d4fc82babd21631f194d370409ceb2e40b2f0b5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -4029,7 +4468,10 @@ name = "revive-dt-common"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"moka",
|
||||||
|
"once_cell",
|
||||||
"semver 1.0.26",
|
"semver 1.0.26",
|
||||||
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4070,9 +4512,12 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy",
|
"alloy",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bson",
|
||||||
|
"cacache",
|
||||||
"clap",
|
"clap",
|
||||||
"futures",
|
"futures",
|
||||||
"indexmap 2.10.0",
|
"indexmap 2.10.0",
|
||||||
|
"once_cell",
|
||||||
"revive-dt-common",
|
"revive-dt-common",
|
||||||
"revive-dt-compiler",
|
"revive-dt-compiler",
|
||||||
"revive-dt-config",
|
"revive-dt-config",
|
||||||
@@ -4081,7 +4526,10 @@ dependencies = [
|
|||||||
"revive-dt-node-interaction",
|
"revive-dt-node-interaction",
|
||||||
"revive-dt-report",
|
"revive-dt-report",
|
||||||
"semver 1.0.26",
|
"semver 1.0.26",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"temp-dir",
|
"temp-dir",
|
||||||
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@@ -4095,6 +4543,8 @@ dependencies = [
|
|||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"alloy-sol-types",
|
"alloy-sol-types",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"regex",
|
||||||
|
"revive-common",
|
||||||
"revive-dt-common",
|
"revive-dt-common",
|
||||||
"semver 1.0.26",
|
"semver 1.0.26",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -4109,6 +4559,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy",
|
"alloy",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"revive-common",
|
||||||
"revive-dt-common",
|
"revive-dt-common",
|
||||||
"revive-dt-config",
|
"revive-dt-config",
|
||||||
"revive-dt-format",
|
"revive-dt-format",
|
||||||
@@ -4135,6 +4586,7 @@ name = "revive-dt-report"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"revive-dt-common",
|
||||||
"revive-dt-compiler",
|
"revive-dt-compiler",
|
||||||
"revive-dt-config",
|
"revive-dt-config",
|
||||||
"revive-dt-format",
|
"revive-dt-format",
|
||||||
@@ -4421,6 +4873,12 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scoped-tls"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -4575,6 +5033,7 @@ version = "1.0.140"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"indexmap 2.10.0",
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
"ryu",
|
"ryu",
|
||||||
@@ -4618,7 +5077,7 @@ version = "3.12.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
|
checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
"hex",
|
"hex",
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
@@ -4652,6 +5111,28 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha-1"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest 0.10.7",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha1"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest 0.10.7",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.9.9"
|
version = "0.9.9"
|
||||||
@@ -5126,6 +5607,23 @@ dependencies = [
|
|||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ssri"
|
||||||
|
version = "9.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da7a2b3c2bc9693bcb40870c4e9b5bf0d79f9cb46273321bf855ec513e919082"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
"digest 0.10.7",
|
||||||
|
"hex",
|
||||||
|
"miette",
|
||||||
|
"serde",
|
||||||
|
"sha-1",
|
||||||
|
"sha2 0.10.9",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"xxhash-rust",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stable_deref_trait"
|
name = "stable_deref_trait"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -5273,6 +5771,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tagptr"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tap"
|
name = "tap"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -5770,6 +6274,12 @@ dependencies = [
|
|||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
@@ -5805,12 +6315,30 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.3",
|
||||||
|
"js-sys",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "value-bag"
|
||||||
|
version = "1.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
@@ -6066,6 +6594,28 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.61.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
|
||||||
|
dependencies = [
|
||||||
|
"windows-collections",
|
||||||
|
"windows-core",
|
||||||
|
"windows-future",
|
||||||
|
"windows-link",
|
||||||
|
"windows-numerics",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-collections"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
@@ -6079,6 +6629,17 @@ dependencies = [
|
|||||||
"windows-strings 0.4.2",
|
"windows-strings 0.4.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-future"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core",
|
||||||
|
"windows-link",
|
||||||
|
"windows-threading",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-implement"
|
name = "windows-implement"
|
||||||
version = "0.60.0"
|
version = "0.60.0"
|
||||||
@@ -6103,9 +6664,19 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.1.1"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-numerics"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-registry"
|
name = "windows-registry"
|
||||||
@@ -6195,6 +6766,15 @@ dependencies = [
|
|||||||
"windows_x86_64_msvc 0.53.0",
|
"windows_x86_64_msvc 0.53.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-threading"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -6324,6 +6904,12 @@ dependencies = [
|
|||||||
"tap",
|
"tap",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xxhash-rust"
|
||||||
|
version = "0.8.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yansi"
|
name = "yansi"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|||||||
+7
-1
@@ -8,7 +8,7 @@ authors = ["Parity Technologies <admin@parity.io>"]
|
|||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
repository = "https://github.com/paritytech/revive-differential-testing.git"
|
repository = "https://github.com/paritytech/revive-differential-testing.git"
|
||||||
rust-version = "1.85.0"
|
rust-version = "1.87.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
revive-dt-common = { version = "0.1.0", path = "crates/common" }
|
revive-dt-common = { version = "0.1.0", path = "crates/common" }
|
||||||
@@ -25,10 +25,14 @@ revive-dt-solc-binaries = { version = "0.1.0", path = "crates/solc-binaries" }
|
|||||||
alloy-primitives = "1.2.1"
|
alloy-primitives = "1.2.1"
|
||||||
alloy-sol-types = "1.2.1"
|
alloy-sol-types = "1.2.1"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
bson = { version = "2.15.0" }
|
||||||
|
cacache = { version = "13.1.0" }
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
foundry-compilers-artifacts = { version = "0.18.0" }
|
foundry-compilers-artifacts = { version = "0.18.0" }
|
||||||
futures = { version = "0.3.31" }
|
futures = { version = "0.3.31" }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
|
regex = "1"
|
||||||
|
moka = "0.12.10"
|
||||||
reqwest = { version = "0.12.15", features = ["json"] }
|
reqwest = { version = "0.12.15", features = ["json"] }
|
||||||
once_cell = "1.21"
|
once_cell = "1.21"
|
||||||
semver = { version = "1.0", features = ["serde"] }
|
semver = { version = "1.0", features = ["serde"] }
|
||||||
@@ -36,12 +40,14 @@ serde = { version = "1.0", default-features = false, features = ["derive"] }
|
|||||||
serde_json = { version = "1.0", default-features = false, features = [
|
serde_json = { version = "1.0", default-features = false, features = [
|
||||||
"arbitrary_precision",
|
"arbitrary_precision",
|
||||||
"std",
|
"std",
|
||||||
|
"unbounded_depth",
|
||||||
] }
|
] }
|
||||||
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"
|
||||||
temp-dir = { version = "0.1.16" }
|
temp-dir = { version = "0.1.16" }
|
||||||
tempfile = "3.3"
|
tempfile = "3.3"
|
||||||
|
thiserror = "2"
|
||||||
tokio = { version = "1.47.0", default-features = false, features = [
|
tokio = { version = "1.47.0", default-features = false, features = [
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"process",
|
"process",
|
||||||
|
|||||||
@@ -1,13 +1,24 @@
|
|||||||
{
|
{
|
||||||
"modes": [
|
"modes": [
|
||||||
"Y >=0.8.9",
|
"Y >=0.8.9",
|
||||||
"E",
|
"E"
|
||||||
"I"
|
|
||||||
],
|
],
|
||||||
"cases": [
|
"cases": [
|
||||||
{
|
{
|
||||||
"name": "first",
|
"name": "first",
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "0xdeadbeef00000000000000000000000000000042",
|
||||||
|
"expected_balance": "1233"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0xdeadbeef00000000000000000000000000000042",
|
||||||
|
"is_storage_empty": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0xdeadbeef00000000000000000000000000000042",
|
||||||
|
"is_storage_empty": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"instance": "WBTC_1",
|
"instance": "WBTC_1",
|
||||||
"method": "#deployer",
|
"method": "#deployer",
|
||||||
|
|||||||
@@ -10,5 +10,8 @@ rust-version.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
moka = { workspace = true, features = ["sync"] }
|
||||||
|
once_cell = { workspace = true }
|
||||||
semver = { workspace = true }
|
semver = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
tokio = { workspace = true, default-features = false, features = ["time"] }
|
tokio = { workspace = true, default-features = false, features = ["time"] }
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
//! This module implements a cached file system allowing for results to be stored in-memory rather
|
||||||
|
//! rather being queried from the file system again.
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::io::{Error, Result};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use moka::sync::Cache;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
pub fn read(path: impl AsRef<Path>) -> Result<Vec<u8>> {
|
||||||
|
static READ_CACHE: Lazy<Cache<PathBuf, Vec<u8>>> = Lazy::new(|| Cache::new(10_000));
|
||||||
|
|
||||||
|
let path = path.as_ref().canonicalize()?;
|
||||||
|
match READ_CACHE.get(path.as_path()) {
|
||||||
|
Some(content) => Ok(content),
|
||||||
|
None => {
|
||||||
|
let content = fs::read(path.as_path())?;
|
||||||
|
READ_CACHE.insert(path, content.clone());
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_to_string(path: impl AsRef<Path>) -> Result<String> {
|
||||||
|
let content = read(path)?;
|
||||||
|
String::from_utf8(content).map_err(|_| {
|
||||||
|
Error::new(
|
||||||
|
std::io::ErrorKind::InvalidData,
|
||||||
|
"The contents of the file are not valid UTF8",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_dir(path: impl AsRef<Path>) -> Result<Box<dyn Iterator<Item = Result<PathBuf>>>> {
|
||||||
|
static READ_DIR_CACHE: Lazy<Cache<PathBuf, Vec<PathBuf>>> = Lazy::new(|| Cache::new(10_000));
|
||||||
|
|
||||||
|
let path = path.as_ref().canonicalize()?;
|
||||||
|
match READ_DIR_CACHE.get(path.as_path()) {
|
||||||
|
Some(entries) => Ok(Box::new(entries.into_iter().map(Ok)) as Box<_>),
|
||||||
|
None => {
|
||||||
|
let entries = fs::read_dir(path.as_path())?
|
||||||
|
.flat_map(|maybe_entry| maybe_entry.map(|entry| entry.path()))
|
||||||
|
.collect();
|
||||||
|
READ_DIR_CACHE.insert(path.clone(), entries);
|
||||||
|
Ok(read_dir(path).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
use std::{borrow::Cow, collections::HashSet, path::PathBuf};
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
collections::HashSet,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
/// An iterator that finds files of a certain extension in the provided directory. You can think of
|
/// An iterator that finds files of a certain extension in the provided directory. You can think of
|
||||||
/// this a glob pattern similar to: `${path}/**/*.md`
|
/// this a glob pattern similar to: `${path}/**/*.md`
|
||||||
@@ -15,14 +19,20 @@ pub struct FilesWithExtensionIterator {
|
|||||||
/// this vector then they will be returned when the [`Iterator::next`] method is called. If not
|
/// this vector then they will be returned when the [`Iterator::next`] method is called. If not
|
||||||
/// then we visit one of the next directories to visit.
|
/// then we visit one of the next directories to visit.
|
||||||
files_matching_allowed_extensions: Vec<PathBuf>,
|
files_matching_allowed_extensions: Vec<PathBuf>,
|
||||||
|
|
||||||
|
/// This option controls if the the cached file system should be used or not. This could be
|
||||||
|
/// better for certain cases where the entries in the directories do not change and therefore
|
||||||
|
/// caching can be used.
|
||||||
|
use_cached_fs: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilesWithExtensionIterator {
|
impl FilesWithExtensionIterator {
|
||||||
pub fn new(root_directory: PathBuf) -> Self {
|
pub fn new(root_directory: impl AsRef<Path>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
allowed_extensions: Default::default(),
|
allowed_extensions: Default::default(),
|
||||||
directories_to_search: vec![root_directory],
|
directories_to_search: vec![root_directory.as_ref().to_path_buf()],
|
||||||
files_matching_allowed_extensions: Default::default(),
|
files_matching_allowed_extensions: Default::default(),
|
||||||
|
use_cached_fs: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +43,11 @@ impl FilesWithExtensionIterator {
|
|||||||
self.allowed_extensions.insert(allowed_extension.into());
|
self.allowed_extensions.insert(allowed_extension.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_use_cached_fs(mut self, use_cached_fs: bool) -> Self {
|
||||||
|
self.use_cached_fs = use_cached_fs;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for FilesWithExtensionIterator {
|
impl Iterator for FilesWithExtensionIterator {
|
||||||
@@ -45,16 +60,19 @@ impl Iterator for FilesWithExtensionIterator {
|
|||||||
|
|
||||||
let directory_to_search = self.directories_to_search.pop()?;
|
let directory_to_search = self.directories_to_search.pop()?;
|
||||||
|
|
||||||
// Read all of the entries in the directory. If we failed to read this dir's entires then we
|
let iterator = if self.use_cached_fs {
|
||||||
// elect to just ignore it and look in the next directory, we do that by calling the next
|
let Ok(dir_entries) = crate::cached_fs::read_dir(directory_to_search.as_path()) else {
|
||||||
// method again on the iterator, which is an intentional decision that we made here instead
|
return self.next();
|
||||||
// of panicking.
|
};
|
||||||
|
Box::new(dir_entries) as Box<dyn Iterator<Item = std::io::Result<PathBuf>>>
|
||||||
|
} else {
|
||||||
let Ok(dir_entries) = std::fs::read_dir(directory_to_search) else {
|
let Ok(dir_entries) = std::fs::read_dir(directory_to_search) else {
|
||||||
return self.next();
|
return self.next();
|
||||||
};
|
};
|
||||||
|
Box::new(dir_entries.map(|maybe_entry| maybe_entry.map(|entry| entry.path()))) as Box<_>
|
||||||
|
};
|
||||||
|
|
||||||
for entry in dir_entries.flatten() {
|
for entry_path in iterator.flatten() {
|
||||||
let entry_path = entry.path();
|
|
||||||
if entry_path.is_dir() {
|
if entry_path.is_dir() {
|
||||||
self.directories_to_search.push(entry_path)
|
self.directories_to_search.push(entry_path)
|
||||||
} else if entry_path.is_file()
|
} else if entry_path.is_file()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
//! This crate provides common concepts, functionality, types, macros, and more that other crates in
|
//! This crate provides common concepts, functionality, types, macros, and more that other crates in
|
||||||
//! the workspace can benefit from.
|
//! the workspace can benefit from.
|
||||||
|
|
||||||
|
pub mod cached_fs;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod futures;
|
pub mod futures;
|
||||||
pub mod iterators;
|
pub mod iterators;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
mod mode;
|
||||||
mod version_or_requirement;
|
mod version_or_requirement;
|
||||||
|
|
||||||
|
pub use mode::*;
|
||||||
pub use version_or_requirement::*;
|
pub use version_or_requirement::*;
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
use crate::types::VersionOrRequirement;
|
||||||
|
use semver::Version;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
/// This represents a mode that a given test should be run with, if possible.
|
||||||
|
///
|
||||||
|
/// We obtain this by taking a [`ParsedMode`], which may be looser or more strict
|
||||||
|
/// in its requirements, and then expanding it out into a list of [`Mode`]s.
|
||||||
|
///
|
||||||
|
/// Use [`ParsedMode::to_test_modes()`] to do this.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct Mode {
|
||||||
|
pub pipeline: ModePipeline,
|
||||||
|
pub optimize_setting: ModeOptimizerSetting,
|
||||||
|
pub version: Option<semver::VersionReq>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Mode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.pipeline.fmt(f)?;
|
||||||
|
f.write_str(" ")?;
|
||||||
|
self.optimize_setting.fmt(f)?;
|
||||||
|
|
||||||
|
if let Some(version) = &self.version {
|
||||||
|
f.write_str(" ")?;
|
||||||
|
version.fmt(f)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mode {
|
||||||
|
/// Return all of the available mode combinations.
|
||||||
|
pub fn all() -> impl Iterator<Item = Mode> {
|
||||||
|
ModePipeline::test_cases().flat_map(|pipeline| {
|
||||||
|
ModeOptimizerSetting::test_cases().map(move |optimize_setting| Mode {
|
||||||
|
pipeline,
|
||||||
|
optimize_setting,
|
||||||
|
version: None,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves the [`Mode`]'s solidity version requirement into a [`VersionOrRequirement`] if
|
||||||
|
/// the requirement is present on the object. Otherwise, the passed default version is used.
|
||||||
|
pub fn compiler_version_to_use(&self, default: Version) -> VersionOrRequirement {
|
||||||
|
match self.version {
|
||||||
|
Some(ref requirement) => requirement.clone().into(),
|
||||||
|
None => default.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What do we want the compiler to do?
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum ModePipeline {
|
||||||
|
/// Compile Solidity code via Yul IR
|
||||||
|
ViaYulIR,
|
||||||
|
/// Compile Solidity direct to assembly
|
||||||
|
ViaEVMAssembly,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ModePipeline {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
// via Yul IR
|
||||||
|
"Y" => Ok(ModePipeline::ViaYulIR),
|
||||||
|
// Don't go via Yul IR
|
||||||
|
"E" => Ok(ModePipeline::ViaEVMAssembly),
|
||||||
|
// Anything else that we see isn't a mode at all
|
||||||
|
_ => Err(anyhow::anyhow!(
|
||||||
|
"Unsupported pipeline '{s}': expected 'Y' or 'E'"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ModePipeline {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ModePipeline::ViaYulIR => f.write_str("Y"),
|
||||||
|
ModePipeline::ViaEVMAssembly => f.write_str("E"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModePipeline {
|
||||||
|
/// Should we go via Yul IR?
|
||||||
|
pub fn via_yul_ir(&self) -> bool {
|
||||||
|
matches!(self, ModePipeline::ViaYulIR)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over the available pipelines that we'd like to test,
|
||||||
|
/// when an explicit pipeline was not specified.
|
||||||
|
pub fn test_cases() -> impl Iterator<Item = ModePipeline> + Clone {
|
||||||
|
[ModePipeline::ViaYulIR, ModePipeline::ViaEVMAssembly].into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum ModeOptimizerSetting {
|
||||||
|
/// 0 / -: Don't apply any optimizations
|
||||||
|
M0,
|
||||||
|
/// 1: Apply less than default optimizations
|
||||||
|
M1,
|
||||||
|
/// 2: Apply the default optimizations
|
||||||
|
M2,
|
||||||
|
/// 3 / +: Apply aggressive optimizations
|
||||||
|
M3,
|
||||||
|
/// s: Optimize for size
|
||||||
|
Ms,
|
||||||
|
/// z: Aggressively optimize for size
|
||||||
|
Mz,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ModeOptimizerSetting {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"M0" => Ok(ModeOptimizerSetting::M0),
|
||||||
|
"M1" => Ok(ModeOptimizerSetting::M1),
|
||||||
|
"M2" => Ok(ModeOptimizerSetting::M2),
|
||||||
|
"M3" => Ok(ModeOptimizerSetting::M3),
|
||||||
|
"Ms" => Ok(ModeOptimizerSetting::Ms),
|
||||||
|
"Mz" => Ok(ModeOptimizerSetting::Mz),
|
||||||
|
_ => Err(anyhow::anyhow!(
|
||||||
|
"Unsupported optimizer setting '{s}': expected 'M0', 'M1', 'M2', 'M3', 'Ms' or 'Mz'"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ModeOptimizerSetting {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ModeOptimizerSetting::M0 => f.write_str("M0"),
|
||||||
|
ModeOptimizerSetting::M1 => f.write_str("M1"),
|
||||||
|
ModeOptimizerSetting::M2 => f.write_str("M2"),
|
||||||
|
ModeOptimizerSetting::M3 => f.write_str("M3"),
|
||||||
|
ModeOptimizerSetting::Ms => f.write_str("Ms"),
|
||||||
|
ModeOptimizerSetting::Mz => f.write_str("Mz"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModeOptimizerSetting {
|
||||||
|
/// An iterator over the available optimizer settings that we'd like to test,
|
||||||
|
/// when an explicit optimizer setting was not specified.
|
||||||
|
pub fn test_cases() -> impl Iterator<Item = ModeOptimizerSetting> + Clone {
|
||||||
|
[
|
||||||
|
// No optimizations:
|
||||||
|
ModeOptimizerSetting::M0,
|
||||||
|
// Aggressive optimizations:
|
||||||
|
ModeOptimizerSetting::M3,
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Are any optimizations enabled?
|
||||||
|
pub fn optimizations_enabled(&self) -> bool {
|
||||||
|
!matches!(self, ModeOptimizerSetting::M0)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
use semver::Version;
|
||||||
|
|
||||||
|
/// This is the first version of solc that supports the `--via-ir` flag / "viaIR" input JSON.
|
||||||
|
pub const SOLC_VERSION_SUPPORTING_VIA_YUL_IR: Version = Version::new(0, 8, 13);
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
//! - Polkadot revive resolc compiler
|
//! - Polkadot revive resolc compiler
|
||||||
//! - Polkadot revive Wasm compiler
|
//! - Polkadot revive Wasm compiler
|
||||||
|
|
||||||
|
mod constants;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs::read_to_string,
|
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
@@ -16,9 +17,13 @@ use semver::Version;
|
|||||||
use serde::{Deserialize, Serialize};
|
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::types::VersionOrRequirement;
|
use revive_dt_common::types::VersionOrRequirement;
|
||||||
use revive_dt_config::Arguments;
|
use revive_dt_config::Arguments;
|
||||||
|
|
||||||
|
// Re-export this as it's a part of the compiler interface.
|
||||||
|
pub use revive_dt_common::types::{Mode, ModeOptimizerSetting, ModePipeline};
|
||||||
|
|
||||||
pub mod revive_js;
|
pub mod revive_js;
|
||||||
pub mod revive_resolc;
|
pub mod revive_resolc;
|
||||||
pub mod solc;
|
pub mod solc;
|
||||||
@@ -43,18 +48,26 @@ pub trait SolidityCompiler {
|
|||||||
) -> impl Future<Output = anyhow::Result<PathBuf>>;
|
) -> impl Future<Output = anyhow::Result<PathBuf>>;
|
||||||
|
|
||||||
fn version(&self) -> anyhow::Result<Version>;
|
fn version(&self) -> anyhow::Result<Version>;
|
||||||
|
|
||||||
|
/// Does the compiler support the provided mode and version settings?
|
||||||
|
fn supports_mode(
|
||||||
|
compiler_version: &Version,
|
||||||
|
optimize_setting: ModeOptimizerSetting,
|
||||||
|
pipeline: ModePipeline,
|
||||||
|
) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The generic compilation input configuration.
|
/// The generic compilation input configuration.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct CompilerInput {
|
pub struct CompilerInput {
|
||||||
pub enable_optimization: Option<bool>,
|
pub pipeline: Option<ModePipeline>,
|
||||||
pub via_ir: Option<bool>,
|
pub optimization: Option<ModeOptimizerSetting>,
|
||||||
pub evm_version: Option<EVMVersion>,
|
pub evm_version: Option<EVMVersion>,
|
||||||
pub allow_paths: Vec<PathBuf>,
|
pub allow_paths: Vec<PathBuf>,
|
||||||
pub base_path: Option<PathBuf>,
|
pub base_path: Option<PathBuf>,
|
||||||
pub sources: HashMap<PathBuf, String>,
|
pub sources: HashMap<PathBuf, String>,
|
||||||
pub libraries: HashMap<PathBuf, HashMap<String, Address>>,
|
pub libraries: HashMap<PathBuf, HashMap<String, Address>>,
|
||||||
|
pub revert_string_handling: Option<RevertString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The generic compilation output configuration.
|
/// The generic compilation output configuration.
|
||||||
@@ -84,25 +97,26 @@ where
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
input: CompilerInput {
|
input: CompilerInput {
|
||||||
enable_optimization: Default::default(),
|
pipeline: Default::default(),
|
||||||
via_ir: Default::default(),
|
optimization: Default::default(),
|
||||||
evm_version: Default::default(),
|
evm_version: Default::default(),
|
||||||
allow_paths: Default::default(),
|
allow_paths: Default::default(),
|
||||||
base_path: Default::default(),
|
base_path: Default::default(),
|
||||||
sources: Default::default(),
|
sources: Default::default(),
|
||||||
libraries: Default::default(),
|
libraries: Default::default(),
|
||||||
|
revert_string_handling: Default::default(),
|
||||||
},
|
},
|
||||||
additional_options: T::Options::default(),
|
additional_options: T::Options::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_optimization(mut self, value: impl Into<Option<bool>>) -> Self {
|
pub fn with_optimization(mut self, value: impl Into<Option<ModeOptimizerSetting>>) -> Self {
|
||||||
self.input.enable_optimization = value.into();
|
self.input.optimization = value.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_via_ir(mut self, value: impl Into<Option<bool>>) -> Self {
|
pub fn with_pipeline(mut self, value: impl Into<Option<ModePipeline>>) -> Self {
|
||||||
self.input.via_ir = value.into();
|
self.input.pipeline = value.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,11 +156,27 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_revert_string_handling(
|
||||||
|
mut self,
|
||||||
|
revert_string_handling: impl Into<Option<RevertString>>,
|
||||||
|
) -> Self {
|
||||||
|
self.input.revert_string_handling = revert_string_handling.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_additional_options(mut self, options: impl Into<T::Options>) -> Self {
|
pub fn with_additional_options(mut self, options: impl Into<T::Options>) -> Self {
|
||||||
self.additional_options = options.into();
|
self.additional_options = options.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn then(self, callback: impl FnOnce(Self) -> Self) -> Self {
|
||||||
|
callback(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_then<E>(self, callback: impl FnOnce(Self) -> Result<Self, E>) -> Result<Self, E> {
|
||||||
|
callback(self)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn try_build(
|
pub async fn try_build(
|
||||||
self,
|
self,
|
||||||
compiler_path: impl AsRef<Path>,
|
compiler_path: impl AsRef<Path>,
|
||||||
@@ -160,3 +190,15 @@ where
|
|||||||
self.input.clone()
|
self.input.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defines how the compiler should handle revert strings.
|
||||||
|
#[derive(
|
||||||
|
Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
|
pub enum RevertString {
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
Debug,
|
||||||
|
Strip,
|
||||||
|
VerboseDebug,
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ use revive_solc_json_interface::{
|
|||||||
SolcStandardJsonOutput,
|
SolcStandardJsonOutput,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{CompilerInput, CompilerOutput, SolidityCompiler};
|
use super::constants::SOLC_VERSION_SUPPORTING_VIA_YUL_IR;
|
||||||
|
use crate::{CompilerInput, CompilerOutput, ModeOptimizerSetting, ModePipeline, SolidityCompiler};
|
||||||
|
|
||||||
use alloy::json_abi::JsonAbi;
|
use alloy::json_abi::JsonAbi;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
@@ -39,17 +40,25 @@ impl SolidityCompiler for Resolc {
|
|||||||
async fn build(
|
async fn build(
|
||||||
&self,
|
&self,
|
||||||
CompilerInput {
|
CompilerInput {
|
||||||
enable_optimization,
|
pipeline,
|
||||||
// Ignored and not honored since this is required for the resolc compilation.
|
optimization,
|
||||||
via_ir: _via_ir,
|
|
||||||
evm_version,
|
evm_version,
|
||||||
allow_paths,
|
allow_paths,
|
||||||
base_path,
|
base_path,
|
||||||
sources,
|
sources,
|
||||||
libraries,
|
libraries,
|
||||||
|
// TODO: this is currently not being handled since there is no way to pass it into
|
||||||
|
// resolc. So, we need to go back to this later once it's supported.
|
||||||
|
revert_string_handling: _,
|
||||||
}: CompilerInput,
|
}: CompilerInput,
|
||||||
additional_options: Self::Options,
|
additional_options: Self::Options,
|
||||||
) -> anyhow::Result<CompilerOutput> {
|
) -> anyhow::Result<CompilerOutput> {
|
||||||
|
if !matches!(pipeline, None | Some(ModePipeline::ViaYulIR)) {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Resolc only supports the Y (via Yul IR) pipeline, but the provided pipeline is {pipeline:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let input = SolcStandardJsonInput {
|
let input = SolcStandardJsonInput {
|
||||||
language: SolcStandardJsonInputLanguage::Solidity,
|
language: SolcStandardJsonInputLanguage::Solidity,
|
||||||
sources: sources
|
sources: sources
|
||||||
@@ -78,7 +87,9 @@ impl SolidityCompiler for Resolc {
|
|||||||
output_selection: Some(SolcStandardJsonInputSettingsSelection::new_required()),
|
output_selection: Some(SolcStandardJsonInputSettingsSelection::new_required()),
|
||||||
via_ir: Some(true),
|
via_ir: Some(true),
|
||||||
optimizer: SolcStandardJsonInputSettingsOptimizer::new(
|
optimizer: SolcStandardJsonInputSettingsOptimizer::new(
|
||||||
enable_optimization.unwrap_or(false),
|
optimization
|
||||||
|
.unwrap_or(ModeOptimizerSetting::M0)
|
||||||
|
.optimizations_enabled(),
|
||||||
None,
|
None,
|
||||||
&Version::new(0, 0, 0),
|
&Version::new(0, 0, 0),
|
||||||
false,
|
false,
|
||||||
@@ -229,6 +240,18 @@ impl SolidityCompiler for Resolc {
|
|||||||
|
|
||||||
Version::parse(version_string).map_err(Into::into)
|
Version::parse(version_string).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_mode(
|
||||||
|
compiler_version: &Version,
|
||||||
|
_optimize_setting: ModeOptimizerSetting,
|
||||||
|
pipeline: ModePipeline,
|
||||||
|
) -> bool {
|
||||||
|
// We only support the Y (IE compile via Yul IR) mode here, which also means that we can
|
||||||
|
// only use solc version 0.8.13 and above. We must always compile via Yul IR as resolc
|
||||||
|
// needs this to translate to LLVM IR and then RISCV.
|
||||||
|
pipeline == ModePipeline::ViaYulIR
|
||||||
|
&& compiler_version >= &SOLC_VERSION_SUPPORTING_VIA_YUL_IR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ use revive_dt_common::types::VersionOrRequirement;
|
|||||||
use revive_dt_config::Arguments;
|
use revive_dt_config::Arguments;
|
||||||
use revive_dt_solc_binaries::download_solc;
|
use revive_dt_solc_binaries::download_solc;
|
||||||
|
|
||||||
use crate::{CompilerInput, CompilerOutput, SolidityCompiler};
|
use super::constants::SOLC_VERSION_SUPPORTING_VIA_YUL_IR;
|
||||||
|
use crate::{CompilerInput, CompilerOutput, ModeOptimizerSetting, ModePipeline, SolidityCompiler};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use foundry_compilers_artifacts::{
|
use foundry_compilers_artifacts::{
|
||||||
@@ -35,16 +36,28 @@ impl SolidityCompiler for Solc {
|
|||||||
async fn build(
|
async fn build(
|
||||||
&self,
|
&self,
|
||||||
CompilerInput {
|
CompilerInput {
|
||||||
enable_optimization,
|
pipeline,
|
||||||
via_ir,
|
optimization,
|
||||||
evm_version,
|
evm_version,
|
||||||
allow_paths,
|
allow_paths,
|
||||||
base_path,
|
base_path,
|
||||||
sources,
|
sources,
|
||||||
libraries,
|
libraries,
|
||||||
|
revert_string_handling,
|
||||||
}: CompilerInput,
|
}: CompilerInput,
|
||||||
_: Self::Options,
|
_: Self::Options,
|
||||||
) -> anyhow::Result<CompilerOutput> {
|
) -> anyhow::Result<CompilerOutput> {
|
||||||
|
let compiler_supports_via_ir = self.version()? >= SOLC_VERSION_SUPPORTING_VIA_YUL_IR;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// `supports_mode` is called prior to instantiating a compiler, we should never
|
||||||
|
// ask for something which is invalid.
|
||||||
|
let via_ir = match (pipeline, compiler_supports_via_ir) {
|
||||||
|
(pipeline, true) => pipeline.map(|p| p.via_yul_ir()),
|
||||||
|
(_pipeline, false) => None,
|
||||||
|
};
|
||||||
|
|
||||||
let input = SolcInput {
|
let input = SolcInput {
|
||||||
language: SolcLanguage::Solidity,
|
language: SolcLanguage::Solidity,
|
||||||
sources: Sources(
|
sources: Sources(
|
||||||
@@ -55,7 +68,7 @@ impl SolidityCompiler for Solc {
|
|||||||
),
|
),
|
||||||
settings: Settings {
|
settings: Settings {
|
||||||
optimizer: Optimizer {
|
optimizer: Optimizer {
|
||||||
enabled: enable_optimization,
|
enabled: optimization.map(|o| o.optimizations_enabled()),
|
||||||
details: Some(Default::default()),
|
details: Some(Default::default()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -87,6 +100,15 @@ impl SolidityCompiler for Solc {
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
},
|
},
|
||||||
|
debug: revert_string_handling.map(|revert_string_handling| DebuggingSettings {
|
||||||
|
revert_strings: match revert_string_handling {
|
||||||
|
crate::RevertString::Default => Some(RevertStrings::Default),
|
||||||
|
crate::RevertString::Debug => Some(RevertStrings::Debug),
|
||||||
|
crate::RevertString::Strip => Some(RevertStrings::Strip),
|
||||||
|
crate::RevertString::VerboseDebug => Some(RevertStrings::VerboseDebug),
|
||||||
|
},
|
||||||
|
debug_info: Default::default(),
|
||||||
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -212,6 +234,18 @@ impl SolidityCompiler for Solc {
|
|||||||
|
|
||||||
Version::parse(version_string).map_err(Into::into)
|
Version::parse(version_string).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_mode(
|
||||||
|
compiler_version: &Version,
|
||||||
|
_optimize_setting: ModeOptimizerSetting,
|
||||||
|
pipeline: ModePipeline,
|
||||||
|
) -> bool {
|
||||||
|
// solc 0.8.13 and above supports --via-ir, and less than that does not. Thus, we support mode E
|
||||||
|
// (ie no Yul IR) in either case, but only support Y (via Yul IR) if the compiler is new enough.
|
||||||
|
pipeline == ModePipeline::ViaEVMAssembly
|
||||||
|
|| (pipeline == ModePipeline::ViaYulIR
|
||||||
|
&& compiler_version >= &SOLC_VERSION_SUPPORTING_VIA_YUL_IR)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -58,10 +58,6 @@ pub struct Arguments {
|
|||||||
#[arg(long = "geth-start-timeout", default_value = "5000")]
|
#[arg(long = "geth-start-timeout", default_value = "5000")]
|
||||||
pub geth_start_timeout: u64,
|
pub geth_start_timeout: u64,
|
||||||
|
|
||||||
/// The test network chain ID.
|
|
||||||
#[arg(short, long = "network-id", default_value = "420420420")]
|
|
||||||
pub network_id: u64,
|
|
||||||
|
|
||||||
/// Configure nodes according to this genesis.json file.
|
/// Configure nodes according to this genesis.json file.
|
||||||
#[arg(long = "genesis", default_value = "genesis.json")]
|
#[arg(long = "genesis", default_value = "genesis.json")]
|
||||||
pub genesis_file: PathBuf,
|
pub genesis_file: PathBuf,
|
||||||
@@ -96,10 +92,19 @@ pub struct Arguments {
|
|||||||
#[arg(long, default_value = "1")]
|
#[arg(long, default_value = "1")]
|
||||||
pub number_of_nodes: usize,
|
pub number_of_nodes: usize,
|
||||||
|
|
||||||
/// Determines the amount of threads that will will be used.
|
/// Determines the amount of tokio worker threads that will will be used.
|
||||||
#[arg(long, default_value = "12")]
|
#[arg(
|
||||||
|
long,
|
||||||
|
default_value_t = std::thread::available_parallelism()
|
||||||
|
.map(|n| n.get())
|
||||||
|
.unwrap_or(1)
|
||||||
|
)]
|
||||||
pub number_of_threads: usize,
|
pub number_of_threads: usize,
|
||||||
|
|
||||||
|
/// Determines the amount of concurrent tasks that will be spawned to run tests. Defaults to 10 x the number of nodes.
|
||||||
|
#[arg(long)]
|
||||||
|
pub number_concurrent_tasks: Option<usize>,
|
||||||
|
|
||||||
/// Extract problems back to the test corpus.
|
/// Extract problems back to the test corpus.
|
||||||
#[arg(short, long = "extract-problems")]
|
#[arg(short, long = "extract-problems")]
|
||||||
pub extract_problems: bool,
|
pub extract_problems: bool,
|
||||||
@@ -115,6 +120,10 @@ pub struct Arguments {
|
|||||||
/// By default it uses `eth-rpc` binary found in `$PATH`.
|
/// By default it uses `eth-rpc` binary found in `$PATH`.
|
||||||
#[arg(short = 'p', long = "eth_proxy", default_value = "eth-rpc")]
|
#[arg(short = 'p', long = "eth_proxy", default_value = "eth-rpc")]
|
||||||
pub eth_proxy: PathBuf,
|
pub eth_proxy: PathBuf,
|
||||||
|
|
||||||
|
/// Controls if the compilation cache should be invalidated or not.
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub invalidate_compilation_cache: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Arguments {
|
impl Arguments {
|
||||||
@@ -134,6 +143,13 @@ impl Arguments {
|
|||||||
panic!("should have a workdir configured")
|
panic!("should have a workdir configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the number of concurrent tasks to run. This is provided via the
|
||||||
|
/// `--number-concurrent-tasks` argument, and otherwise defaults to --number-of-nodes * 20.
|
||||||
|
pub fn number_of_concurrent_tasks(&self) -> usize {
|
||||||
|
self.number_concurrent_tasks
|
||||||
|
.unwrap_or(20 * self.number_of_nodes)
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to parse `self.account` into a [PrivateKeySigner],
|
/// Try to parse `self.account` into a [PrivateKeySigner],
|
||||||
/// panicing on error.
|
/// panicing on error.
|
||||||
pub fn wallet(&self) -> EthereumWallet {
|
pub fn wallet(&self) -> EthereumWallet {
|
||||||
|
|||||||
@@ -23,11 +23,17 @@ revive-dt-report = { workspace = true }
|
|||||||
|
|
||||||
alloy = { workspace = true }
|
alloy = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
bson = { workspace = true }
|
||||||
|
cacache = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
|
once_cell = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
semver = { workspace = true }
|
semver = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
temp-dir = { workspace = true }
|
temp-dir = { workspace = true }
|
||||||
|
tempfile = { workspace = true }
|
||||||
|
|||||||
@@ -0,0 +1,261 @@
|
|||||||
|
//! A wrapper around the compiler which allows for caching of compilation artifacts so that they can
|
||||||
|
//! be reused between runs.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use futures::FutureExt;
|
||||||
|
use revive_dt_common::iterators::FilesWithExtensionIterator;
|
||||||
|
use revive_dt_compiler::{Compiler, CompilerOutput, Mode, SolidityCompiler};
|
||||||
|
use revive_dt_config::Arguments;
|
||||||
|
use revive_dt_format::metadata::{ContractIdent, ContractInstance, Metadata};
|
||||||
|
|
||||||
|
use alloy::{hex::ToHexExt, json_abi::JsonAbi, primitives::Address};
|
||||||
|
use anyhow::{Error, Result};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use semver::Version;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::sync::{Mutex, RwLock};
|
||||||
|
use tracing::{Instrument, debug, debug_span, instrument};
|
||||||
|
|
||||||
|
use crate::Platform;
|
||||||
|
|
||||||
|
pub struct CachedCompiler(ArtifactsCache);
|
||||||
|
|
||||||
|
impl CachedCompiler {
|
||||||
|
pub async fn new(path: impl AsRef<Path>, invalidate_cache: bool) -> Result<Self> {
|
||||||
|
let mut cache = ArtifactsCache::new(path);
|
||||||
|
if invalidate_cache {
|
||||||
|
cache = cache.with_invalidated_cache().await?;
|
||||||
|
}
|
||||||
|
Ok(Self(cache))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compiles or gets the compilation artifacts from the cache.
|
||||||
|
#[instrument(
|
||||||
|
level = "debug",
|
||||||
|
skip_all,
|
||||||
|
fields(
|
||||||
|
metadata_file_path = %metadata_file_path.as_ref().display(),
|
||||||
|
%mode,
|
||||||
|
platform = P::config_id().to_string()
|
||||||
|
),
|
||||||
|
err
|
||||||
|
)]
|
||||||
|
pub async fn compile_contracts<P: Platform>(
|
||||||
|
&self,
|
||||||
|
metadata: &Metadata,
|
||||||
|
metadata_file_path: impl AsRef<Path>,
|
||||||
|
mode: &Mode,
|
||||||
|
config: &Arguments,
|
||||||
|
deployed_libraries: Option<&HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
|
||||||
|
) -> Result<(CompilerOutput, Version)> {
|
||||||
|
static CACHE_KEY_LOCK: Lazy<RwLock<HashMap<CacheKey, Arc<Mutex<()>>>>> =
|
||||||
|
Lazy::new(Default::default);
|
||||||
|
|
||||||
|
let compiler_version_or_requirement = mode.compiler_version_to_use(config.solc.clone());
|
||||||
|
let compiler_path = <P::Compiler as SolidityCompiler>::get_compiler_executable(
|
||||||
|
config,
|
||||||
|
compiler_version_or_requirement,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let compiler_version =
|
||||||
|
<P::Compiler as SolidityCompiler>::new(compiler_path.clone()).version()?;
|
||||||
|
|
||||||
|
let cache_key = CacheKey {
|
||||||
|
platform_key: P::config_id().to_string(),
|
||||||
|
compiler_version: compiler_version.clone(),
|
||||||
|
metadata_file_path: metadata_file_path.as_ref().to_path_buf(),
|
||||||
|
solc_mode: mode.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let compilation_callback = || {
|
||||||
|
async move {
|
||||||
|
compile_contracts::<P>(
|
||||||
|
metadata.directory()?,
|
||||||
|
compiler_path,
|
||||||
|
metadata.files_to_compile()?,
|
||||||
|
mode,
|
||||||
|
deployed_libraries,
|
||||||
|
)
|
||||||
|
.map(|compilation_result| compilation_result.map(CacheValue::new))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
.instrument(debug_span!(
|
||||||
|
"Running compilation for the cache key",
|
||||||
|
cache_key.platform_key = %cache_key.platform_key,
|
||||||
|
cache_key.compiler_version = %cache_key.compiler_version,
|
||||||
|
cache_key.metadata_file_path = %cache_key.metadata_file_path.display(),
|
||||||
|
cache_key.solc_mode = %cache_key.solc_mode,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
let compiled_contracts = match deployed_libraries {
|
||||||
|
// If deployed libraries have been specified then we will re-compile the contract as it
|
||||||
|
// means that linking is required in this case.
|
||||||
|
Some(_) => {
|
||||||
|
debug!("Deployed libraries defined, recompilation must take place");
|
||||||
|
debug!("Cache miss");
|
||||||
|
compilation_callback().await?.compiler_output
|
||||||
|
}
|
||||||
|
// If no deployed libraries are specified then we can follow the cached flow and attempt
|
||||||
|
// to lookup the compilation artifacts in the cache.
|
||||||
|
None => {
|
||||||
|
debug!("Deployed libraries undefined, attempting to make use of cache");
|
||||||
|
|
||||||
|
// Lock this specific cache key such that we do not get inconsistent state. We want
|
||||||
|
// that when multiple cases come in asking for the compilation artifacts then they
|
||||||
|
// don't all trigger a compilation if there's a cache miss. Hence, the lock here.
|
||||||
|
let read_guard = CACHE_KEY_LOCK.read().await;
|
||||||
|
let mutex = match read_guard.get(&cache_key).cloned() {
|
||||||
|
Some(value) => value,
|
||||||
|
None => {
|
||||||
|
drop(read_guard);
|
||||||
|
CACHE_KEY_LOCK
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.entry(cache_key.clone())
|
||||||
|
.or_default()
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let _guard = mutex.lock().await;
|
||||||
|
|
||||||
|
self.0
|
||||||
|
.get_or_insert_with(&cache_key, compilation_callback)
|
||||||
|
.await
|
||||||
|
.map(|value| value.compiler_output)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((compiled_contracts, compiler_version))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn compile_contracts<P: Platform>(
|
||||||
|
metadata_directory: impl AsRef<Path>,
|
||||||
|
compiler_path: impl AsRef<Path>,
|
||||||
|
mut files_to_compile: impl Iterator<Item = PathBuf>,
|
||||||
|
mode: &Mode,
|
||||||
|
deployed_libraries: Option<&HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
|
||||||
|
) -> Result<CompilerOutput> {
|
||||||
|
let all_sources_in_dir = FilesWithExtensionIterator::new(metadata_directory.as_ref())
|
||||||
|
.with_allowed_extension("sol")
|
||||||
|
.with_use_cached_fs(true)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Compiler::<P::Compiler>::new()
|
||||||
|
.with_allow_path(metadata_directory)
|
||||||
|
// Handling the modes
|
||||||
|
.with_optimization(mode.optimize_setting)
|
||||||
|
.with_pipeline(mode.pipeline)
|
||||||
|
// Adding the contract sources to the compiler.
|
||||||
|
.try_then(|compiler| {
|
||||||
|
files_to_compile.try_fold(compiler, |compiler, path| compiler.with_source(path))
|
||||||
|
})?
|
||||||
|
// Adding the deployed libraries to the compiler.
|
||||||
|
.then(|compiler| {
|
||||||
|
deployed_libraries
|
||||||
|
.iter()
|
||||||
|
.flat_map(|value| value.iter())
|
||||||
|
.map(|(instance, (ident, address, abi))| (instance, ident, address, abi))
|
||||||
|
.flat_map(|(_, ident, address, _)| {
|
||||||
|
all_sources_in_dir
|
||||||
|
.iter()
|
||||||
|
.map(move |path| (ident, address, path))
|
||||||
|
})
|
||||||
|
.fold(compiler, |compiler, (ident, address, path)| {
|
||||||
|
compiler.with_library(path, ident.as_str(), *address)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.try_build(compiler_path)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ArtifactsCache {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArtifactsCache {
|
||||||
|
pub fn new(path: impl AsRef<Path>) -> Self {
|
||||||
|
Self {
|
||||||
|
path: path.as_ref().to_path_buf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", skip_all, err)]
|
||||||
|
pub async fn with_invalidated_cache(self) -> Result<Self> {
|
||||||
|
cacache::clear(self.path.as_path())
|
||||||
|
.await
|
||||||
|
.map_err(Into::<Error>::into)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", skip_all, err)]
|
||||||
|
pub async fn insert(&self, key: &CacheKey, value: &CacheValue) -> Result<()> {
|
||||||
|
let key = bson::to_vec(key)?;
|
||||||
|
let value = bson::to_vec(value)?;
|
||||||
|
cacache::write(self.path.as_path(), key.encode_hex(), value).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(&self, key: &CacheKey) -> Option<CacheValue> {
|
||||||
|
let key = bson::to_vec(key).ok()?;
|
||||||
|
let value = cacache::read(self.path.as_path(), key.encode_hex())
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
let value = bson::from_slice::<CacheValue>(&value).ok()?;
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", skip_all, err)]
|
||||||
|
pub async fn get_or_insert_with(
|
||||||
|
&self,
|
||||||
|
key: &CacheKey,
|
||||||
|
callback: impl AsyncFnOnce() -> Result<CacheValue>,
|
||||||
|
) -> Result<CacheValue> {
|
||||||
|
match self.get(key).await {
|
||||||
|
Some(value) => {
|
||||||
|
debug!("Cache hit");
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
debug!("Cache miss");
|
||||||
|
let value = callback().await?;
|
||||||
|
self.insert(key, &value).await?;
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
struct CacheKey {
|
||||||
|
/// The platform name that this artifact was compiled for. For example, this could be EVM or
|
||||||
|
/// PVM.
|
||||||
|
platform_key: String,
|
||||||
|
|
||||||
|
/// The version of the compiler that was used to compile the artifacts.
|
||||||
|
compiler_version: Version,
|
||||||
|
|
||||||
|
/// The path of the metadata file that the compilation artifacts are for.
|
||||||
|
metadata_file_path: PathBuf,
|
||||||
|
|
||||||
|
/// The mode that the compilation artifacts where compiled with.
|
||||||
|
solc_mode: Mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
struct CacheValue {
|
||||||
|
/// The compiler output from the compilation run.
|
||||||
|
compiler_output: CompilerOutput,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CacheValue {
|
||||||
|
pub fn new(compiler_output: CompilerOutput) -> Self {
|
||||||
|
Self { compiler_output }
|
||||||
|
}
|
||||||
|
}
|
||||||
+229
-38
@@ -4,14 +4,15 @@ use std::collections::HashMap;
|
|||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
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::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, GethDebugTracerType, GethDebugTracingOptions, GethTrace,
|
CallFrame, GethDebugBuiltInTracerType, GethDebugTracerConfig, GethDebugTracerType,
|
||||||
PreStateConfig,
|
GethDebugTracingOptions, GethTrace, PreStateConfig,
|
||||||
};
|
};
|
||||||
use alloy::{
|
use alloy::{
|
||||||
primitives::Address,
|
primitives::Address,
|
||||||
@@ -26,9 +27,12 @@ use revive_dt_format::traits::{ResolutionContext, ResolverApi};
|
|||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
use revive_dt_format::case::{Case, CaseIdx};
|
use revive_dt_format::case::{Case, CaseIdx};
|
||||||
use revive_dt_format::input::{Calldata, EtherValue, Expected, ExpectedOutput, Method};
|
use revive_dt_format::input::{
|
||||||
use revive_dt_format::metadata::{ContractInstance, ContractPathAndIdent};
|
BalanceAssertion, Calldata, EtherValue, Expected, ExpectedOutput, Input, Method,
|
||||||
use revive_dt_format::{input::Input, metadata::Metadata};
|
StorageEmptyAssertion,
|
||||||
|
};
|
||||||
|
use revive_dt_format::metadata::{ContractIdent, ContractInstance, ContractPathAndIdent};
|
||||||
|
use revive_dt_format::{input::Step, metadata::Metadata};
|
||||||
use revive_dt_node::Node;
|
use revive_dt_node::Node;
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
use tracing::Instrument;
|
use tracing::Instrument;
|
||||||
@@ -40,7 +44,7 @@ pub struct CaseState<T: Platform> {
|
|||||||
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
||||||
|
|
||||||
/// This map stores the contracts deployments for this case.
|
/// This map stores the contracts deployments for this case.
|
||||||
deployed_contracts: HashMap<ContractInstance, (Address, JsonAbi)>,
|
deployed_contracts: HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>,
|
||||||
|
|
||||||
/// This map stores the variables used for each one of the cases contained in the metadata
|
/// This map stores the variables used for each one of the cases contained in the metadata
|
||||||
/// file.
|
/// file.
|
||||||
@@ -59,7 +63,7 @@ where
|
|||||||
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, (Address, JsonAbi)>,
|
deployed_contracts: HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
compiled_contracts,
|
compiled_contracts,
|
||||||
@@ -70,6 +74,32 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn handle_step(
|
||||||
|
&mut self,
|
||||||
|
metadata: &Metadata,
|
||||||
|
case_idx: CaseIdx,
|
||||||
|
step: &Step,
|
||||||
|
node: &T::Blockchain,
|
||||||
|
) -> anyhow::Result<StepOutput> {
|
||||||
|
match step {
|
||||||
|
Step::FunctionCall(input) => {
|
||||||
|
let (receipt, geth_trace, diff_mode) =
|
||||||
|
self.handle_input(metadata, case_idx, input, node).await?;
|
||||||
|
Ok(StepOutput::FunctionCall(receipt, geth_trace, diff_mode))
|
||||||
|
}
|
||||||
|
Step::BalanceAssertion(balance_assertion) => {
|
||||||
|
self.handle_balance_assertion(metadata, case_idx, balance_assertion, node)
|
||||||
|
.await?;
|
||||||
|
Ok(StepOutput::BalanceAssertion)
|
||||||
|
}
|
||||||
|
Step::StorageEmptyAssertion(storage_empty) => {
|
||||||
|
self.handle_storage_empty(metadata, case_idx, storage_empty, node)
|
||||||
|
.await?;
|
||||||
|
Ok(StepOutput::StorageEmptyAssertion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn handle_input(
|
pub async fn handle_input(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
@@ -78,7 +108,7 @@ where
|
|||||||
node: &T::Blockchain,
|
node: &T::Blockchain,
|
||||||
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
||||||
let deployment_receipts = self
|
let deployment_receipts = self
|
||||||
.handle_contract_deployment(metadata, case_idx, input, node)
|
.handle_input_contract_deployment(metadata, case_idx, input, node)
|
||||||
.await?;
|
.await?;
|
||||||
let execution_receipt = self
|
let execution_receipt = self
|
||||||
.handle_input_execution(input, deployment_receipts, node)
|
.handle_input_execution(input, deployment_receipts, node)
|
||||||
@@ -93,21 +123,42 @@ where
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles the contract deployment for a given input performing it if it needs to be performed.
|
pub async fn handle_balance_assertion(
|
||||||
async fn handle_contract_deployment(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
case_idx: CaseIdx,
|
_: CaseIdx,
|
||||||
|
balance_assertion: &BalanceAssertion,
|
||||||
|
node: &T::Blockchain,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
self.handle_balance_assertion_contract_deployment(metadata, balance_assertion, node)
|
||||||
|
.await?;
|
||||||
|
self.handle_balance_assertion_execution(balance_assertion, node)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_storage_empty(
|
||||||
|
&mut self,
|
||||||
|
metadata: &Metadata,
|
||||||
|
_: CaseIdx,
|
||||||
|
storage_empty: &StorageEmptyAssertion,
|
||||||
|
node: &T::Blockchain,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
self.handle_storage_empty_assertion_contract_deployment(metadata, storage_empty, node)
|
||||||
|
.await?;
|
||||||
|
self.handle_storage_empty_assertion_execution(storage_empty, node)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles the contract deployment for a given input performing it if it needs to be performed.
|
||||||
|
async fn handle_input_contract_deployment(
|
||||||
|
&mut self,
|
||||||
|
metadata: &Metadata,
|
||||||
|
_: CaseIdx,
|
||||||
input: &Input,
|
input: &Input,
|
||||||
node: &T::Blockchain,
|
node: &T::Blockchain,
|
||||||
) -> anyhow::Result<HashMap<ContractInstance, TransactionReceipt>> {
|
) -> anyhow::Result<HashMap<ContractInstance, TransactionReceipt>> {
|
||||||
let span = tracing::debug_span!(
|
|
||||||
"Handling contract deployment",
|
|
||||||
?case_idx,
|
|
||||||
instance = ?input.instance
|
|
||||||
);
|
|
||||||
let _guard = span.enter();
|
|
||||||
|
|
||||||
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() {
|
||||||
if !self.deployed_contracts.contains_key(&instance) {
|
if !self.deployed_contracts.contains_key(&instance) {
|
||||||
@@ -205,6 +256,11 @@ where
|
|||||||
tracer: Some(GethDebugTracerType::BuiltInTracer(
|
tracer: Some(GethDebugTracerType::BuiltInTracer(
|
||||||
GethDebugBuiltInTracerType::CallTracer,
|
GethDebugBuiltInTracerType::CallTracer,
|
||||||
)),
|
)),
|
||||||
|
tracer_config: GethDebugTracerConfig(serde_json::json! {{
|
||||||
|
"onlyTopCall": true,
|
||||||
|
"withLog": false,
|
||||||
|
"withReturnData": false
|
||||||
|
}}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -253,9 +309,6 @@ where
|
|||||||
resolver: &impl ResolverApi,
|
resolver: &impl ResolverApi,
|
||||||
tracing_result: &CallFrame,
|
tracing_result: &CallFrame,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let span = tracing::info_span!("Handling input expectations");
|
|
||||||
let _guard = span.enter();
|
|
||||||
|
|
||||||
// Resolving the `input.expected` into a series of expectations that we can then assert on.
|
// 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 {
|
Input {
|
||||||
@@ -445,9 +498,6 @@ where
|
|||||||
execution_receipt: TransactionReceipt,
|
execution_receipt: TransactionReceipt,
|
||||||
node: &T::Blockchain,
|
node: &T::Blockchain,
|
||||||
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
||||||
let span = tracing::info_span!("Handling input diff");
|
|
||||||
let _guard = span.enter();
|
|
||||||
|
|
||||||
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,
|
||||||
@@ -462,6 +512,128 @@ where
|
|||||||
Ok((execution_receipt, trace, diff))
|
Ok((execution_receipt, trace, diff))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn handle_balance_assertion_contract_deployment(
|
||||||
|
&mut self,
|
||||||
|
metadata: &Metadata,
|
||||||
|
balance_assertion: &BalanceAssertion,
|
||||||
|
node: &T::Blockchain,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let Some(instance) = balance_assertion
|
||||||
|
.address
|
||||||
|
.strip_suffix(".address")
|
||||||
|
.map(ContractInstance::new)
|
||||||
|
else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
self.get_or_deploy_contract_instance(
|
||||||
|
&instance,
|
||||||
|
metadata,
|
||||||
|
Input::default_caller(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
node,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_balance_assertion_execution(
|
||||||
|
&mut self,
|
||||||
|
BalanceAssertion {
|
||||||
|
address: address_string,
|
||||||
|
expected_balance: amount,
|
||||||
|
..
|
||||||
|
}: &BalanceAssertion,
|
||||||
|
node: &T::Blockchain,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let address = Address::from_slice(
|
||||||
|
Calldata::new_compound([address_string])
|
||||||
|
.calldata(node, self.default_resolution_context())
|
||||||
|
.await?
|
||||||
|
.get(12..32)
|
||||||
|
.expect("Can't fail"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let balance = node.balance_of(address).await?;
|
||||||
|
|
||||||
|
let expected = *amount;
|
||||||
|
let actual = balance;
|
||||||
|
if expected != actual {
|
||||||
|
tracing::error!(%expected, %actual, %address, "Balance assertion failed");
|
||||||
|
anyhow::bail!(
|
||||||
|
"Balance assertion failed - Expected {} but got {} for {} resolved to {}",
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
address_string,
|
||||||
|
address,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_storage_empty_assertion_contract_deployment(
|
||||||
|
&mut self,
|
||||||
|
metadata: &Metadata,
|
||||||
|
storage_empty_assertion: &StorageEmptyAssertion,
|
||||||
|
node: &T::Blockchain,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let Some(instance) = storage_empty_assertion
|
||||||
|
.address
|
||||||
|
.strip_suffix(".address")
|
||||||
|
.map(ContractInstance::new)
|
||||||
|
else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
self.get_or_deploy_contract_instance(
|
||||||
|
&instance,
|
||||||
|
metadata,
|
||||||
|
Input::default_caller(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
node,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_storage_empty_assertion_execution(
|
||||||
|
&mut self,
|
||||||
|
StorageEmptyAssertion {
|
||||||
|
address: address_string,
|
||||||
|
is_storage_empty,
|
||||||
|
..
|
||||||
|
}: &StorageEmptyAssertion,
|
||||||
|
node: &T::Blockchain,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let address = Address::from_slice(
|
||||||
|
Calldata::new_compound([address_string])
|
||||||
|
.calldata(node, self.default_resolution_context())
|
||||||
|
.await?
|
||||||
|
.get(12..32)
|
||||||
|
.expect("Can't fail"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let storage = node.latest_state_proof(address, Default::default()).await?;
|
||||||
|
let is_empty = storage.storage_hash == EMPTY_ROOT_HASH;
|
||||||
|
|
||||||
|
let expected = is_storage_empty;
|
||||||
|
let actual = is_empty;
|
||||||
|
|
||||||
|
if *expected != actual {
|
||||||
|
tracing::error!(%expected, %actual, %address, "Storage Empty Assertion failed");
|
||||||
|
anyhow::bail!(
|
||||||
|
"Storage Empty Assertion failed - Expected {} but got {} for {} resolved to {}",
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
address_string,
|
||||||
|
address,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the information of a deployed contract or library from the state. If it's found to not
|
/// Gets the information of a deployed contract or library from the state. If it's found to not
|
||||||
/// be deployed then it will be deployed.
|
/// be deployed then it will be deployed.
|
||||||
///
|
///
|
||||||
@@ -477,7 +649,7 @@ where
|
|||||||
value: Option<EtherValue>,
|
value: Option<EtherValue>,
|
||||||
node: &T::Blockchain,
|
node: &T::Blockchain,
|
||||||
) -> 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,8 +733,10 @@ where
|
|||||||
"Deployed contract"
|
"Deployed contract"
|
||||||
);
|
);
|
||||||
|
|
||||||
self.deployed_contracts
|
self.deployed_contracts.insert(
|
||||||
.insert(contract_instance.clone(), (address, abi.clone()));
|
contract_instance.clone(),
|
||||||
|
(contract_ident, address, abi.clone()),
|
||||||
|
);
|
||||||
|
|
||||||
Ok((address, abi, Some(receipt)))
|
Ok((address, abi, Some(receipt)))
|
||||||
}
|
}
|
||||||
@@ -651,21 +825,25 @@ where
|
|||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut inputs_executed = 0;
|
let mut steps_executed = 0;
|
||||||
for (input_idx, input) in self.case.inputs_iterator().enumerate() {
|
for (step_idx, step) in self.case.steps_iterator().enumerate() {
|
||||||
let tracing_span = tracing::info_span!("Handling input", input_idx);
|
let tracing_span = tracing::info_span!("Handling input", step_idx);
|
||||||
|
|
||||||
let (leader_receipt, _, leader_diff) = self
|
let leader_step_output = self
|
||||||
.leader_state
|
.leader_state
|
||||||
.handle_input(self.metadata, self.case_idx, &input, self.leader_node)
|
.handle_step(self.metadata, self.case_idx, &step, self.leader_node)
|
||||||
.instrument(tracing_span.clone())
|
.instrument(tracing_span.clone())
|
||||||
.await?;
|
.await?;
|
||||||
let (follower_receipt, _, follower_diff) = self
|
let follower_step_output = self
|
||||||
.follower_state
|
.follower_state
|
||||||
.handle_input(self.metadata, self.case_idx, &input, self.follower_node)
|
.handle_step(self.metadata, self.case_idx, &step, self.follower_node)
|
||||||
.instrument(tracing_span)
|
.instrument(tracing_span)
|
||||||
.await?;
|
.await?;
|
||||||
|
match (leader_step_output, follower_step_output) {
|
||||||
|
(
|
||||||
|
StepOutput::FunctionCall(leader_receipt, _, leader_diff),
|
||||||
|
StepOutput::FunctionCall(follower_receipt, _, follower_diff),
|
||||||
|
) => {
|
||||||
if leader_diff == follower_diff {
|
if leader_diff == follower_diff {
|
||||||
tracing::debug!("State diffs match between leader and follower.");
|
tracing::debug!("State diffs match between leader and follower.");
|
||||||
} else {
|
} else {
|
||||||
@@ -679,10 +857,23 @@ where
|
|||||||
tracing::trace!("Leader logs: {:?}", leader_receipt.logs());
|
tracing::trace!("Leader logs: {:?}", leader_receipt.logs());
|
||||||
tracing::trace!("Follower logs: {:?}", follower_receipt.logs());
|
tracing::trace!("Follower logs: {:?}", follower_receipt.logs());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
inputs_executed += 1;
|
(StepOutput::BalanceAssertion, StepOutput::BalanceAssertion) => {}
|
||||||
|
(StepOutput::StorageEmptyAssertion, StepOutput::StorageEmptyAssertion) => {}
|
||||||
|
_ => unreachable!("The two step outputs can not be of a different kind"),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(inputs_executed)
|
steps_executed += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(steps_executed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
pub enum StepOutput {
|
||||||
|
FunctionCall(TransactionReceipt, GethTrace, DiffMode),
|
||||||
|
BalanceAssertion,
|
||||||
|
StorageEmptyAssertion,
|
||||||
|
}
|
||||||
|
|||||||
+301
-363
@@ -1,28 +1,28 @@
|
|||||||
|
mod cached_compiler;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::Path,
|
path::{Path, PathBuf},
|
||||||
sync::{Arc, LazyLock},
|
sync::{Arc, LazyLock},
|
||||||
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
use alloy::{
|
use alloy::{
|
||||||
json_abi::JsonAbi,
|
|
||||||
network::{Ethereum, TransactionBuilder},
|
network::{Ethereum, TransactionBuilder},
|
||||||
primitives::Address,
|
|
||||||
rpc::types::TransactionRequest,
|
rpc::types::TransactionRequest,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use futures::StreamExt;
|
use futures::stream::futures_unordered::FuturesUnordered;
|
||||||
use revive_dt_common::iterators::FilesWithExtensionIterator;
|
use futures::{Stream, StreamExt};
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
use semver::Version;
|
|
||||||
use temp_dir::TempDir;
|
use temp_dir::TempDir;
|
||||||
use tokio::sync::{Mutex, RwLock};
|
use tokio::sync::mpsc;
|
||||||
use tracing::{Instrument, Level};
|
use tracing::{Instrument, Level};
|
||||||
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
||||||
|
|
||||||
|
use revive_dt_common::types::Mode;
|
||||||
use revive_dt_compiler::SolidityCompiler;
|
use revive_dt_compiler::SolidityCompiler;
|
||||||
use revive_dt_compiler::{Compiler, CompilerOutput};
|
|
||||||
use revive_dt_config::*;
|
use revive_dt_config::*;
|
||||||
use revive_dt_core::{
|
use revive_dt_core::{
|
||||||
Geth, Kitchensink, Platform,
|
Geth, Kitchensink, Platform,
|
||||||
@@ -31,23 +31,28 @@ use revive_dt_core::{
|
|||||||
use revive_dt_format::{
|
use revive_dt_format::{
|
||||||
case::{Case, CaseIdx},
|
case::{Case, CaseIdx},
|
||||||
corpus::Corpus,
|
corpus::Corpus,
|
||||||
input::Input,
|
input::{Input, Step},
|
||||||
metadata::{ContractInstance, ContractPathAndIdent, Metadata, MetadataFile},
|
metadata::{ContractPathAndIdent, Metadata, MetadataFile},
|
||||||
mode::SolcMode,
|
|
||||||
};
|
};
|
||||||
use revive_dt_node::pool::NodePool;
|
use revive_dt_node::pool::NodePool;
|
||||||
use revive_dt_report::reporter::{Report, Span};
|
use revive_dt_report::reporter::{Report, Span};
|
||||||
|
|
||||||
|
use crate::cached_compiler::CachedCompiler;
|
||||||
|
|
||||||
static TEMP_DIR: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
|
static TEMP_DIR: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
|
||||||
|
|
||||||
type CompilationCache<'a> = Arc<
|
/// this represents a single "test"; a mode, path and collection of cases.
|
||||||
RwLock<
|
#[derive(Clone)]
|
||||||
HashMap<
|
struct Test {
|
||||||
(&'a Path, SolcMode, TestingPlatform),
|
metadata: Metadata,
|
||||||
Arc<Mutex<Option<Arc<(Version, CompilerOutput)>>>>,
|
path: PathBuf,
|
||||||
>,
|
mode: Mode,
|
||||||
>,
|
case_idx: CaseIdx,
|
||||||
>;
|
case: Case,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This represents the results that we gather from running test cases.
|
||||||
|
type CaseResult = Result<usize, anyhow::Error>;
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let args = init_cli()?;
|
let args = init_cli()?;
|
||||||
@@ -119,7 +124,7 @@ fn collect_corpora(args: &Arguments) -> anyhow::Result<HashMap<Corpus, Vec<Metad
|
|||||||
|
|
||||||
async fn run_driver<L, F>(
|
async fn run_driver<L, F>(
|
||||||
args: &Arguments,
|
args: &Arguments,
|
||||||
tests: &[MetadataFile],
|
metadata_files: &[MetadataFile],
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> anyhow::Result<()>
|
) -> anyhow::Result<()>
|
||||||
where
|
where
|
||||||
@@ -128,10 +133,28 @@ where
|
|||||||
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||||
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let leader_nodes = NodePool::<L::Blockchain>::new(args)?;
|
let (report_tx, report_rx) = mpsc::unbounded_channel::<(Test, CaseResult)>();
|
||||||
let follower_nodes = NodePool::<F::Blockchain>::new(args)?;
|
|
||||||
|
|
||||||
let test_cases = tests
|
let tests = prepare_tests::<L, F>(args, metadata_files);
|
||||||
|
let driver_task = start_driver_task::<L, F>(args, tests, span, report_tx).await?;
|
||||||
|
let status_reporter_task = start_reporter_task(report_rx);
|
||||||
|
|
||||||
|
tokio::join!(status_reporter_task, driver_task);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_tests<L, F>(
|
||||||
|
args: &Arguments,
|
||||||
|
metadata_files: &[MetadataFile],
|
||||||
|
) -> impl Stream<Item = Test>
|
||||||
|
where
|
||||||
|
L: Platform,
|
||||||
|
F: Platform,
|
||||||
|
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||||
|
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
metadata_files
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(
|
.flat_map(
|
||||||
|MetadataFile {
|
|MetadataFile {
|
||||||
@@ -176,183 +199,223 @@ where
|
|||||||
Some(false) | None => true,
|
Some(false) | None => true,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.collect::<Vec<_>>();
|
.filter(|(metadata_file_path, metadata, ..)| match metadata.required_evm_version {
|
||||||
|
Some(evm_version_requirement) => {
|
||||||
|
let is_allowed = evm_version_requirement
|
||||||
|
.matches(&<L::Blockchain as revive_dt_node::Node>::evm_version())
|
||||||
|
&& evm_version_requirement
|
||||||
|
.matches(&<F::Blockchain as revive_dt_node::Node>::evm_version());
|
||||||
|
|
||||||
let metadata_case_status = Arc::new(RwLock::new(test_cases.iter().fold(
|
if !is_allowed {
|
||||||
HashMap::<_, HashMap<_, _>>::new(),
|
tracing::warn!(
|
||||||
|mut map, (path, _, case_idx, case, solc_mode)| {
|
metadata_file_path = %metadata_file_path.display(),
|
||||||
map.entry((path.to_path_buf(), solc_mode.clone()))
|
leader_evm_version = %<L::Blockchain as revive_dt_node::Node>::evm_version(),
|
||||||
.or_default()
|
follower_evm_version = %<F::Blockchain as revive_dt_node::Node>::evm_version(),
|
||||||
.insert((CaseIdx::new(*case_idx), case.name.clone()), None::<bool>);
|
version_requirement = %evm_version_requirement,
|
||||||
map
|
"Skipped test since the EVM version requirement was not fulfilled."
|
||||||
},
|
|
||||||
)));
|
|
||||||
let status_reporter_task = {
|
|
||||||
let metadata_case_status = metadata_case_status.clone();
|
|
||||||
async move {
|
|
||||||
const GREEN: &str = "\x1B[32m";
|
|
||||||
const RED: &str = "\x1B[31m";
|
|
||||||
const RESET: &str = "\x1B[0m";
|
|
||||||
|
|
||||||
let mut entries_to_delete = Vec::new();
|
|
||||||
let mut number_of_successes = 0;
|
|
||||||
let mut number_of_failures = 0;
|
|
||||||
loop {
|
|
||||||
let metadata_case_status_read = metadata_case_status.read().await;
|
|
||||||
if metadata_case_status_read.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((metadata_file_path, solc_mode), case_status) in
|
|
||||||
metadata_case_status_read.iter()
|
|
||||||
{
|
|
||||||
if case_status.values().any(|value| value.is_none()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let contains_failures = case_status
|
|
||||||
.values()
|
|
||||||
.any(|value| value.is_some_and(|value| !value));
|
|
||||||
|
|
||||||
if !contains_failures {
|
|
||||||
eprintln!(
|
|
||||||
"{}Succeeded:{} {} - {:?}",
|
|
||||||
GREEN,
|
|
||||||
RESET,
|
|
||||||
metadata_file_path.display(),
|
|
||||||
solc_mode
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"{}Failed:{} {} - {:?}",
|
|
||||||
RED,
|
|
||||||
RESET,
|
|
||||||
metadata_file_path.display(),
|
|
||||||
solc_mode
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
number_of_successes += case_status
|
|
||||||
.values()
|
|
||||||
.filter(|value| value.is_some_and(|value| value))
|
|
||||||
.count();
|
|
||||||
number_of_failures += case_status
|
|
||||||
.values()
|
|
||||||
.filter(|value| value.is_some_and(|value| !value))
|
|
||||||
.count();
|
|
||||||
|
|
||||||
let mut case_status = case_status
|
|
||||||
.iter()
|
|
||||||
.map(|((case_idx, case_name), case_status)| {
|
|
||||||
(case_idx.into_inner(), case_name, case_status.unwrap())
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
case_status.sort_by(|a, b| a.0.cmp(&b.0));
|
|
||||||
for (case_idx, case_name, case_status) in case_status.into_iter() {
|
|
||||||
if case_status {
|
|
||||||
eprintln!(
|
|
||||||
" {GREEN}Case Succeeded:{RESET} {} - Case Idx: {case_idx}",
|
|
||||||
case_name
|
|
||||||
.as_ref()
|
|
||||||
.map(|string| string.as_str())
|
|
||||||
.unwrap_or("Unnamed case")
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
" {RED}Case Failed:{RESET} {} - Case Idx: {case_idx}",
|
|
||||||
case_name
|
|
||||||
.as_ref()
|
|
||||||
.map(|string| string.as_str())
|
|
||||||
.unwrap_or("Unnamed case")
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
eprintln!();
|
|
||||||
|
|
||||||
entries_to_delete.push((metadata_file_path.clone(), solc_mode.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(metadata_case_status_read);
|
|
||||||
let mut metadata_case_status_write = metadata_case_status.write().await;
|
|
||||||
for entry in entries_to_delete.drain(..) {
|
|
||||||
metadata_case_status_write.remove(&entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
eprintln!(
|
|
||||||
"{GREEN}{number_of_successes}{RESET} cases succeeded, {RED}{number_of_failures}{RESET} cases failed"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let compilation_cache = Arc::new(RwLock::new(HashMap::new()));
|
is_allowed
|
||||||
let driver_task = futures::stream::iter(test_cases).for_each_concurrent(
|
}
|
||||||
None,
|
None => true,
|
||||||
|(metadata_file_path, metadata, case_idx, case, solc_mode)| {
|
})
|
||||||
let compilation_cache = compilation_cache.clone();
|
.map(|(metadata_file_path, metadata, case_idx, case, solc_mode)| {
|
||||||
|
Test {
|
||||||
|
metadata: metadata.clone(),
|
||||||
|
path: metadata_file_path.to_path_buf(),
|
||||||
|
mode: solc_mode,
|
||||||
|
case_idx: case_idx.into(),
|
||||||
|
case: case.clone(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(async |test| test)
|
||||||
|
.collect::<FuturesUnordered<_>>()
|
||||||
|
.filter_map(async move |test| {
|
||||||
|
// Check that both compilers support this test, else we skip it
|
||||||
|
let is_supported = does_compiler_support_mode::<L>(args, &test.mode).await.ok().unwrap_or(false) &&
|
||||||
|
does_compiler_support_mode::<F>(args, &test.mode).await.ok().unwrap_or(false);
|
||||||
|
|
||||||
|
// We filter_map to avoid needing to clone `test`, but return it as-is.
|
||||||
|
if is_supported {
|
||||||
|
Some(test)
|
||||||
|
} else {
|
||||||
|
tracing::warn!(
|
||||||
|
metadata_file_path = %test.path.display(),
|
||||||
|
case_idx = %test.case_idx,
|
||||||
|
case_name = ?test.case.name,
|
||||||
|
mode = %test.mode,
|
||||||
|
"Skipping test as one or both of the compilers don't support it"
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn does_compiler_support_mode<P: Platform>(
|
||||||
|
args: &Arguments,
|
||||||
|
mode: &Mode,
|
||||||
|
) -> anyhow::Result<bool> {
|
||||||
|
let compiler_version_or_requirement = mode.compiler_version_to_use(args.solc.clone());
|
||||||
|
let compiler_path =
|
||||||
|
P::Compiler::get_compiler_executable(args, compiler_version_or_requirement).await?;
|
||||||
|
let compiler_version = P::Compiler::new(compiler_path.clone()).version()?;
|
||||||
|
|
||||||
|
Ok(P::Compiler::supports_mode(
|
||||||
|
&compiler_version,
|
||||||
|
mode.optimize_setting,
|
||||||
|
mode.pipeline,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_driver_task<L, F>(
|
||||||
|
args: &Arguments,
|
||||||
|
tests: impl Stream<Item = Test>,
|
||||||
|
span: Span,
|
||||||
|
report_tx: mpsc::UnboundedSender<(Test, CaseResult)>,
|
||||||
|
) -> 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,
|
||||||
|
{
|
||||||
|
let leader_nodes = Arc::new(NodePool::<L::Blockchain>::new(args)?);
|
||||||
|
let follower_nodes = Arc::new(NodePool::<F::Blockchain>::new(args)?);
|
||||||
|
let number_concurrent_tasks = args.number_of_concurrent_tasks();
|
||||||
|
let cached_compiler = Arc::new(
|
||||||
|
CachedCompiler::new(
|
||||||
|
args.directory().join("compilation_cache"),
|
||||||
|
args.invalidate_compilation_cache,
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(tests.for_each_concurrent(
|
||||||
|
// We want to limit the concurrent tasks here because:
|
||||||
|
//
|
||||||
|
// 1. We don't want to overwhelm the nodes with too many requests, leading to responses timing out.
|
||||||
|
// 2. We don't want to open too many files at once, leading to the OS running out of file descriptors.
|
||||||
|
//
|
||||||
|
// By default, we allow maximum of 10 ongoing requests per node in order to limit (1), and assume that
|
||||||
|
// this number will automatically be low enough to address (2). The user can override this.
|
||||||
|
Some(number_concurrent_tasks),
|
||||||
|
move |test| {
|
||||||
|
let leader_nodes = leader_nodes.clone();
|
||||||
|
let follower_nodes = follower_nodes.clone();
|
||||||
|
let report_tx = report_tx.clone();
|
||||||
|
let cached_compiler = cached_compiler.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
let leader_node = leader_nodes.round_robbin();
|
let leader_node = leader_nodes.round_robbin();
|
||||||
let follower_node = follower_nodes.round_robbin();
|
let follower_node = follower_nodes.round_robbin();
|
||||||
|
|
||||||
let tracing_span = tracing::span!(
|
let tracing_span = tracing::span!(
|
||||||
Level::INFO,
|
Level::INFO,
|
||||||
"Running driver",
|
"Running driver",
|
||||||
metadata_file_path = %metadata_file_path.display(),
|
metadata_file_path = %test.path.display(),
|
||||||
case_idx = case_idx,
|
case_idx = ?test.case_idx,
|
||||||
solc_mode = ?solc_mode,
|
solc_mode = ?test.mode,
|
||||||
);
|
);
|
||||||
let metadata_case_status = metadata_case_status.clone();
|
|
||||||
async move {
|
|
||||||
let result = handle_case_driver::<L, F>(
|
let result = handle_case_driver::<L, F>(
|
||||||
metadata_file_path.as_path(),
|
&test.path,
|
||||||
metadata,
|
&test.metadata,
|
||||||
case_idx.into(),
|
test.case_idx,
|
||||||
case,
|
&test.case,
|
||||||
solc_mode.clone(),
|
test.mode.clone(),
|
||||||
args,
|
args,
|
||||||
compilation_cache.clone(),
|
cached_compiler,
|
||||||
leader_node,
|
leader_node,
|
||||||
follower_node,
|
follower_node,
|
||||||
span,
|
span,
|
||||||
)
|
)
|
||||||
.await;
|
|
||||||
let mut metadata_case_status = metadata_case_status.write().await;
|
|
||||||
match result {
|
|
||||||
Ok(inputs_executed) => {
|
|
||||||
tracing::info!(inputs_executed, "Execution succeeded");
|
|
||||||
metadata_case_status
|
|
||||||
.entry((metadata_file_path.clone(), solc_mode))
|
|
||||||
.or_default()
|
|
||||||
.insert((CaseIdx::new(case_idx), case.name.clone()), Some(true));
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
metadata_case_status
|
|
||||||
.entry((metadata_file_path.clone(), solc_mode))
|
|
||||||
.or_default()
|
|
||||||
.insert((CaseIdx::new(case_idx), case.name.clone()), Some(false));
|
|
||||||
tracing::error!(%error, "Execution failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tracing::info!("Execution completed");
|
|
||||||
}
|
|
||||||
.instrument(tracing_span)
|
.instrument(tracing_span)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
report_tx
|
||||||
|
.send((test, result))
|
||||||
|
.expect("Failed to send report");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_reporter_task(mut report_rx: mpsc::UnboundedReceiver<(Test, CaseResult)>) {
|
||||||
|
let start = Instant::now();
|
||||||
|
|
||||||
|
const GREEN: &str = "\x1B[32m";
|
||||||
|
const RED: &str = "\x1B[31m";
|
||||||
|
const COLOUR_RESET: &str = "\x1B[0m";
|
||||||
|
const BOLD: &str = "\x1B[1m";
|
||||||
|
const BOLD_RESET: &str = "\x1B[22m";
|
||||||
|
|
||||||
|
let mut number_of_successes = 0;
|
||||||
|
let mut number_of_failures = 0;
|
||||||
|
let mut failures = vec![];
|
||||||
|
|
||||||
|
// Wait for reports to come from our test runner. When the channel closes, this ends.
|
||||||
|
while let Some((test, case_result)) = report_rx.recv().await {
|
||||||
|
let case_name = test.case.name.as_deref().unwrap_or("unnamed_case");
|
||||||
|
let case_idx = test.case_idx;
|
||||||
|
let test_path = test.path.display();
|
||||||
|
let test_mode = test.mode.clone();
|
||||||
|
|
||||||
|
match case_result {
|
||||||
|
Ok(_inputs) => {
|
||||||
|
number_of_successes += 1;
|
||||||
|
eprintln!(
|
||||||
|
"{GREEN}Case Succeeded:{COLOUR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode})"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
number_of_failures += 1;
|
||||||
|
eprintln!(
|
||||||
|
"{RED}Case Failed:{COLOUR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode})"
|
||||||
|
);
|
||||||
|
failures.push((test, err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tokio::join!(status_reporter_task, driver_task);
|
eprintln!();
|
||||||
|
let elapsed = start.elapsed();
|
||||||
|
|
||||||
Ok(())
|
// Now, log the failures with more complete errors at the bottom, like `cargo test` does, so
|
||||||
|
// that we don't have to scroll through the entire output to find them.
|
||||||
|
if !failures.is_empty() {
|
||||||
|
eprintln!("{BOLD}Failures:{BOLD_RESET}\n");
|
||||||
|
|
||||||
|
for failure in failures {
|
||||||
|
let (test, err) = failure;
|
||||||
|
let case_name = test.case.name.as_deref().unwrap_or("unnamed_case");
|
||||||
|
let case_idx = test.case_idx;
|
||||||
|
let test_path = test.path.display();
|
||||||
|
let test_mode = test.mode.clone();
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"---- {RED}Case Failed:{COLOUR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode}) ----\n\n{err}\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary at the end.
|
||||||
|
eprintln!(
|
||||||
|
"{} cases: {GREEN}{number_of_successes}{COLOUR_RESET} cases succeeded, {RED}{number_of_failures}{COLOUR_RESET} cases failed in {} seconds",
|
||||||
|
number_of_successes + number_of_failures,
|
||||||
|
elapsed.as_secs()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn handle_case_driver<'a, L, F>(
|
async fn handle_case_driver<L, F>(
|
||||||
metadata_file_path: &'a Path,
|
metadata_file_path: &Path,
|
||||||
metadata: &'a Metadata,
|
metadata: &Metadata,
|
||||||
case_idx: CaseIdx,
|
case_idx: CaseIdx,
|
||||||
case: &Case,
|
case: &Case,
|
||||||
mode: SolcMode,
|
mode: Mode,
|
||||||
config: &Arguments,
|
config: &Arguments,
|
||||||
compilation_cache: CompilationCache<'a>,
|
cached_compiler: Arc<CachedCompiler>,
|
||||||
leader_node: &L::Blockchain,
|
leader_node: &L::Blockchain,
|
||||||
follower_node: &F::Blockchain,
|
follower_node: &F::Blockchain,
|
||||||
_: Span,
|
_: Span,
|
||||||
@@ -363,27 +426,19 @@ where
|
|||||||
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||||
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let leader_pre_link_contracts = get_or_build_contracts::<L>(
|
let leader_pre_link_contracts = cached_compiler
|
||||||
metadata,
|
.compile_contracts::<L>(metadata, metadata_file_path, &mode, config, None)
|
||||||
metadata_file_path,
|
.await?
|
||||||
mode.clone(),
|
.0
|
||||||
config,
|
.contracts;
|
||||||
compilation_cache.clone(),
|
let follower_pre_link_contracts = cached_compiler
|
||||||
&HashMap::new(),
|
.compile_contracts::<F>(metadata, metadata_file_path, &mode, config, None)
|
||||||
)
|
.await?
|
||||||
.await?;
|
.0
|
||||||
let follower_pre_link_contracts = get_or_build_contracts::<F>(
|
.contracts;
|
||||||
metadata,
|
|
||||||
metadata_file_path,
|
|
||||||
mode.clone(),
|
|
||||||
config,
|
|
||||||
compilation_cache.clone(),
|
|
||||||
&HashMap::new(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut leader_deployed_libraries = HashMap::new();
|
let mut leader_deployed_libraries = None::<HashMap<_, _>>;
|
||||||
let mut follower_deployed_libraries = HashMap::new();
|
let mut follower_deployed_libraries = None::<HashMap<_, _>>;
|
||||||
let mut contract_sources = metadata.contract_sources()?;
|
let mut contract_sources = metadata.contract_sources()?;
|
||||||
for library_instance in metadata
|
for library_instance in metadata
|
||||||
.libraries
|
.libraries
|
||||||
@@ -399,14 +454,10 @@ where
|
|||||||
.context("Failed to find the contract source")?;
|
.context("Failed to find the contract source")?;
|
||||||
|
|
||||||
let (leader_code, leader_abi) = leader_pre_link_contracts
|
let (leader_code, leader_abi) = leader_pre_link_contracts
|
||||||
.1
|
|
||||||
.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")?;
|
.context("Declared library was not compiled")?;
|
||||||
let (follower_code, follower_abi) = follower_pre_link_contracts
|
let (follower_code, follower_abi) = follower_pre_link_contracts
|
||||||
.1
|
|
||||||
.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")?;
|
.context("Declared library was not compiled")?;
|
||||||
@@ -440,9 +491,13 @@ where
|
|||||||
// doing the deployments from different accounts and therefore we're not slowed down by
|
// doing the deployments from different accounts and therefore we're not slowed down by
|
||||||
// the nonce.
|
// the nonce.
|
||||||
let deployer_address = case
|
let deployer_address = case
|
||||||
.inputs
|
.steps
|
||||||
.iter()
|
.iter()
|
||||||
.map(|input| input.caller)
|
.filter_map(|step| match step {
|
||||||
|
Step::FunctionCall(input) => Some(input.caller),
|
||||||
|
Step::BalanceAssertion(..) => None,
|
||||||
|
Step::StorageEmptyAssertion(..) => None,
|
||||||
|
})
|
||||||
.next()
|
.next()
|
||||||
.unwrap_or(Input::default_caller());
|
.unwrap_or(Input::default_caller());
|
||||||
let leader_tx = TransactionBuilder::<Ethereum>::with_deploy_code(
|
let leader_tx = TransactionBuilder::<Ethereum>::with_deploy_code(
|
||||||
@@ -489,81 +544,58 @@ where
|
|||||||
);
|
);
|
||||||
|
|
||||||
let Some(leader_library_address) = leader_receipt.contract_address else {
|
let Some(leader_library_address) = leader_receipt.contract_address else {
|
||||||
tracing::error!("Contract deployment transaction didn't return an address");
|
|
||||||
anyhow::bail!("Contract deployment didn't return an address");
|
anyhow::bail!("Contract deployment didn't return an address");
|
||||||
};
|
};
|
||||||
let Some(follower_library_address) = follower_receipt.contract_address else {
|
let Some(follower_library_address) = follower_receipt.contract_address else {
|
||||||
tracing::error!("Contract deployment transaction didn't return an address");
|
|
||||||
anyhow::bail!("Contract deployment didn't return an address");
|
anyhow::bail!("Contract deployment didn't return an address");
|
||||||
};
|
};
|
||||||
|
|
||||||
leader_deployed_libraries.insert(
|
leader_deployed_libraries.get_or_insert_default().insert(
|
||||||
library_instance.clone(),
|
library_instance.clone(),
|
||||||
(leader_library_address, leader_abi.clone()),
|
(
|
||||||
|
library_ident.clone(),
|
||||||
|
leader_library_address,
|
||||||
|
leader_abi.clone(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
follower_deployed_libraries.insert(
|
follower_deployed_libraries.get_or_insert_default().insert(
|
||||||
library_instance.clone(),
|
library_instance.clone(),
|
||||||
(follower_library_address, follower_abi.clone()),
|
(
|
||||||
|
library_ident,
|
||||||
|
follower_library_address,
|
||||||
|
follower_abi.clone(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let metadata_file_contains_libraries = metadata
|
let (leader_post_link_contracts, leader_compiler_version) = cached_compiler
|
||||||
.libraries
|
.compile_contracts::<L>(
|
||||||
.iter()
|
|
||||||
.flat_map(|map| map.iter())
|
|
||||||
.flat_map(|(_, value)| value.iter())
|
|
||||||
.next()
|
|
||||||
.is_some();
|
|
||||||
let compiled_contracts_require_linking = leader_pre_link_contracts
|
|
||||||
.1
|
|
||||||
.contracts
|
|
||||||
.values()
|
|
||||||
.chain(follower_pre_link_contracts.1.contracts.values())
|
|
||||||
.flat_map(|value| value.values())
|
|
||||||
.any(|(code, _)| !code.chars().all(|char| char.is_ascii_hexdigit()));
|
|
||||||
let (leader_compiled_contracts, follower_compiled_contracts) =
|
|
||||||
if metadata_file_contains_libraries && compiled_contracts_require_linking {
|
|
||||||
let leader_key = (metadata_file_path, mode.clone(), L::config_id());
|
|
||||||
let follower_key = (metadata_file_path, mode.clone(), L::config_id());
|
|
||||||
{
|
|
||||||
let mut cache = compilation_cache.write().await;
|
|
||||||
cache.remove(&leader_key);
|
|
||||||
cache.remove(&follower_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
let leader_post_link_contracts = get_or_build_contracts::<L>(
|
|
||||||
metadata,
|
metadata,
|
||||||
metadata_file_path,
|
metadata_file_path,
|
||||||
mode.clone(),
|
&mode,
|
||||||
config,
|
config,
|
||||||
compilation_cache.clone(),
|
leader_deployed_libraries.as_ref(),
|
||||||
&leader_deployed_libraries,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let follower_post_link_contracts = get_or_build_contracts::<F>(
|
let (follower_post_link_contracts, follower_compiler_version) = cached_compiler
|
||||||
|
.compile_contracts::<F>(
|
||||||
metadata,
|
metadata,
|
||||||
metadata_file_path,
|
metadata_file_path,
|
||||||
mode.clone(),
|
&mode,
|
||||||
config,
|
config,
|
||||||
compilation_cache,
|
follower_deployed_libraries.as_ref(),
|
||||||
&follower_deployed_libraries,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
(leader_post_link_contracts, follower_post_link_contracts)
|
|
||||||
} else {
|
|
||||||
(leader_pre_link_contracts, follower_pre_link_contracts)
|
|
||||||
};
|
|
||||||
|
|
||||||
let leader_state = CaseState::<L>::new(
|
let leader_state = CaseState::<L>::new(
|
||||||
leader_compiled_contracts.0.clone(),
|
leader_compiler_version,
|
||||||
leader_compiled_contracts.1.contracts.clone(),
|
leader_post_link_contracts.contracts,
|
||||||
leader_deployed_libraries,
|
leader_deployed_libraries.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
let follower_state = CaseState::<F>::new(
|
let follower_state = CaseState::<F>::new(
|
||||||
follower_compiled_contracts.0.clone(),
|
follower_compiler_version,
|
||||||
follower_compiled_contracts.1.contracts.clone(),
|
follower_post_link_contracts.contracts,
|
||||||
follower_deployed_libraries,
|
follower_deployed_libraries.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut driver = CaseDriver::<L, F>::new(
|
let mut driver = CaseDriver::<L, F>::new(
|
||||||
@@ -578,112 +610,6 @@ where
|
|||||||
driver.execute().await
|
driver.execute().await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_or_build_contracts<'a, P: Platform>(
|
|
||||||
metadata: &'a Metadata,
|
|
||||||
metadata_file_path: &'a Path,
|
|
||||||
mode: SolcMode,
|
|
||||||
config: &Arguments,
|
|
||||||
compilation_cache: CompilationCache<'a>,
|
|
||||||
deployed_libraries: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
|
||||||
) -> anyhow::Result<Arc<(Version, CompilerOutput)>> {
|
|
||||||
let key = (metadata_file_path, mode.clone(), P::config_id());
|
|
||||||
if let Some(compilation_artifact) = compilation_cache.read().await.get(&key).cloned() {
|
|
||||||
let mut compilation_artifact = compilation_artifact.lock().await;
|
|
||||||
match *compilation_artifact {
|
|
||||||
Some(ref compiled_contracts) => {
|
|
||||||
tracing::debug!(?key, "Compiled contracts cache hit");
|
|
||||||
return Ok(compiled_contracts.clone());
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
tracing::debug!(?key, "Compiled contracts cache miss");
|
|
||||||
let compiled_contracts = Arc::new(
|
|
||||||
compile_contracts::<P>(
|
|
||||||
metadata,
|
|
||||||
metadata_file_path,
|
|
||||||
&mode,
|
|
||||||
config,
|
|
||||||
deployed_libraries,
|
|
||||||
)
|
|
||||||
.await?,
|
|
||||||
);
|
|
||||||
*compilation_artifact = Some(compiled_contracts.clone());
|
|
||||||
return Ok(compiled_contracts.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tracing::debug!(?key, "Compiled contracts cache miss");
|
|
||||||
let mutex = {
|
|
||||||
let mut compilation_cache = compilation_cache.write().await;
|
|
||||||
let mutex = Arc::new(Mutex::new(None));
|
|
||||||
compilation_cache.insert(key, mutex.clone());
|
|
||||||
mutex
|
|
||||||
};
|
|
||||||
let mut compilation_artifact = mutex.lock().await;
|
|
||||||
let compiled_contracts = Arc::new(
|
|
||||||
compile_contracts::<P>(
|
|
||||||
metadata,
|
|
||||||
metadata_file_path,
|
|
||||||
&mode,
|
|
||||||
config,
|
|
||||||
deployed_libraries,
|
|
||||||
)
|
|
||||||
.await?,
|
|
||||||
);
|
|
||||||
*compilation_artifact = Some(compiled_contracts.clone());
|
|
||||||
Ok(compiled_contracts.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn compile_contracts<P: Platform>(
|
|
||||||
metadata: &Metadata,
|
|
||||||
metadata_file_path: &Path,
|
|
||||||
mode: &SolcMode,
|
|
||||||
config: &Arguments,
|
|
||||||
deployed_libraries: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
|
||||||
) -> anyhow::Result<(Version, CompilerOutput)> {
|
|
||||||
let compiler_version_or_requirement = mode.compiler_version_to_use(config.solc.clone());
|
|
||||||
let compiler_path =
|
|
||||||
P::Compiler::get_compiler_executable(config, compiler_version_or_requirement).await?;
|
|
||||||
let compiler_version = P::Compiler::new(compiler_path.clone()).version()?;
|
|
||||||
|
|
||||||
tracing::info!(
|
|
||||||
%compiler_version,
|
|
||||||
metadata_file_path = %metadata_file_path.display(),
|
|
||||||
mode = ?mode,
|
|
||||||
"Compiling contracts"
|
|
||||||
);
|
|
||||||
|
|
||||||
let compiler = Compiler::<P::Compiler>::new()
|
|
||||||
.with_allow_path(metadata.directory()?)
|
|
||||||
.with_optimization(mode.solc_optimize());
|
|
||||||
let mut compiler = metadata
|
|
||||||
.files_to_compile()?
|
|
||||||
.try_fold(compiler, |compiler, path| compiler.with_source(&path))?;
|
|
||||||
for (library_instance, (library_address, _)) in deployed_libraries.iter() {
|
|
||||||
let library_ident = &metadata
|
|
||||||
.contracts
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|contracts| contracts.get(library_instance))
|
|
||||||
.expect("Impossible for library to not be found in contracts")
|
|
||||||
.contract_ident;
|
|
||||||
|
|
||||||
// Note the following: we need to tell solc which files require the libraries to be linked
|
|
||||||
// into them. We do not have access to this information and therefore we choose an easier,
|
|
||||||
// yet more compute intensive route, of telling solc that all of the files need to link the
|
|
||||||
// library and it will only perform the linking for the files that do actually need the
|
|
||||||
// library.
|
|
||||||
compiler = FilesWithExtensionIterator::new(metadata.directory()?)
|
|
||||||
.with_allowed_extension("sol")
|
|
||||||
.fold(compiler, |compiler, path| {
|
|
||||||
compiler.with_library(&path, library_ident.as_str(), *library_address)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let compiler_output = compiler.try_build(compiler_path).await?;
|
|
||||||
|
|
||||||
Ok((compiler_version, compiler_output))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn execute_corpus(
|
async fn execute_corpus(
|
||||||
args: &Arguments,
|
args: &Arguments,
|
||||||
tests: &[MetadataFile],
|
tests: &[MetadataFile],
|
||||||
@@ -715,30 +641,42 @@ async fn compile_corpus(
|
|||||||
.map(move |solc_mode| (metadata, solc_mode))
|
.map(move |solc_mode| (metadata, solc_mode))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
|
||||||
|
let cached_compiler = CachedCompiler::new(file.path(), false)
|
||||||
|
.await
|
||||||
|
.map(Arc::new)
|
||||||
|
.expect("Failed to create the cached compiler");
|
||||||
|
|
||||||
futures::stream::iter(tests)
|
futures::stream::iter(tests)
|
||||||
.for_each_concurrent(None, |(metadata, mode)| async move {
|
.for_each_concurrent(None, |(metadata, mode)| {
|
||||||
|
let cached_compiler = cached_compiler.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
match platform {
|
match platform {
|
||||||
TestingPlatform::Geth => {
|
TestingPlatform::Geth => {
|
||||||
let _ = compile_contracts::<Geth>(
|
let _ = cached_compiler
|
||||||
&metadata.content,
|
.compile_contracts::<Geth>(
|
||||||
&metadata.path,
|
metadata,
|
||||||
|
metadata.path.as_path(),
|
||||||
&mode,
|
&mode,
|
||||||
config,
|
config,
|
||||||
&Default::default(),
|
None,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
TestingPlatform::Kitchensink => {
|
TestingPlatform::Kitchensink => {
|
||||||
let _ = compile_contracts::<Geth>(
|
let _ = cached_compiler
|
||||||
&metadata.content,
|
.compile_contracts::<Kitchensink>(
|
||||||
&metadata.path,
|
metadata,
|
||||||
|
metadata.path.as_path(),
|
||||||
&mode,
|
&mode,
|
||||||
config,
|
config,
|
||||||
&Default::default(),
|
None,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,13 @@ rust-version.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
revive-dt-common = { workspace = true }
|
revive-dt-common = { workspace = true }
|
||||||
|
|
||||||
|
revive-common = { workspace = true }
|
||||||
|
|
||||||
alloy = { workspace = true }
|
alloy = { workspace = true }
|
||||||
alloy-primitives = { workspace = true }
|
alloy-primitives = { workspace = true }
|
||||||
alloy-sol-types = { workspace = true }
|
alloy-sol-types = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
regex = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
semver = { workspace = true }
|
semver = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
|||||||
+37
-13
@@ -1,32 +1,50 @@
|
|||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use revive_dt_common::macros::define_wrapper_type;
|
use revive_dt_common::macros::define_wrapper_type;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
input::{Expected, Input},
|
input::{Expected, Step},
|
||||||
mode::Mode,
|
mode::ParsedMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
|
||||||
pub struct Case {
|
pub struct Case {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
pub modes: Option<Vec<Mode>>,
|
|
||||||
pub inputs: Vec<Input>,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub modes: Option<Vec<ParsedMode>>,
|
||||||
|
|
||||||
|
#[serde(rename = "inputs")]
|
||||||
|
pub steps: Vec<Step>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub group: Option<String>,
|
pub group: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub expected: Option<Expected>,
|
pub expected: Option<Expected>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub ignore: Option<bool>,
|
pub ignore: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Case {
|
impl Case {
|
||||||
pub fn inputs_iterator(&self) -> impl Iterator<Item = Input> {
|
#[allow(irrefutable_let_patterns)]
|
||||||
let inputs_len = self.inputs.len();
|
pub fn steps_iterator(&self) -> impl Iterator<Item = Step> {
|
||||||
self.inputs
|
let steps_len = self.steps.len();
|
||||||
|
self.steps
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(move |(idx, mut input)| {
|
.map(move |(idx, mut step)| {
|
||||||
if idx + 1 == inputs_len {
|
let Step::FunctionCall(ref mut input) = step else {
|
||||||
|
return step;
|
||||||
|
};
|
||||||
|
|
||||||
|
if idx + 1 == steps_len {
|
||||||
if input.expected.is_none() {
|
if input.expected.is_none() {
|
||||||
input.expected = self.expected.clone();
|
input.expected = self.expected.clone();
|
||||||
}
|
}
|
||||||
@@ -36,9 +54,9 @@ impl Case {
|
|||||||
// the case? What are we supposed to do with that final expected field on the
|
// the case? What are we supposed to do with that final expected field on the
|
||||||
// case?
|
// case?
|
||||||
|
|
||||||
input
|
step
|
||||||
} else {
|
} else {
|
||||||
input
|
step
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -49,3 +67,9 @@ define_wrapper_type!(
|
|||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct CaseIdx(usize);
|
pub struct CaseIdx(usize);
|
||||||
);
|
);
|
||||||
|
|
||||||
|
impl std::fmt::Display for CaseIdx {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use revive_dt_common::cached_fs::read_dir;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::metadata::MetadataFile;
|
use crate::metadata::MetadataFile;
|
||||||
@@ -54,7 +55,7 @@ impl Corpus {
|
|||||||
/// `path` is expected to be a directory.
|
/// `path` is expected to be a directory.
|
||||||
pub fn collect_metadata(path: &Path, tests: &mut Vec<MetadataFile>) {
|
pub fn collect_metadata(path: &Path, tests: &mut Vec<MetadataFile>) {
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
let dir_entry = match std::fs::read_dir(path) {
|
let dir_entry = match read_dir(path) {
|
||||||
Ok(dir_entry) => dir_entry,
|
Ok(dir_entry) => dir_entry,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::error!("failed to read dir '{}': {error}", path.display());
|
tracing::error!("failed to read dir '{}': {error}", path.display());
|
||||||
@@ -62,8 +63,8 @@ pub fn collect_metadata(path: &Path, tests: &mut Vec<MetadataFile>) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for entry in dir_entry {
|
for path in dir_entry {
|
||||||
let entry = match entry {
|
let path = match path {
|
||||||
Ok(entry) => entry,
|
Ok(entry) => entry,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::error!("error reading dir entry: {error}");
|
tracing::error!("error reading dir entry: {error}");
|
||||||
@@ -71,7 +72,6 @@ pub fn collect_metadata(path: &Path, tests: &mut Vec<MetadataFile>) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = entry.path();
|
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
collect_metadata(&path, tests);
|
collect_metadata(&path, tests);
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
+103
-22
@@ -3,6 +3,7 @@ use std::collections::HashMap;
|
|||||||
use alloy::{
|
use alloy::{
|
||||||
eips::BlockNumberOrTag,
|
eips::BlockNumberOrTag,
|
||||||
hex::ToHexExt,
|
hex::ToHexExt,
|
||||||
|
json_abi::Function,
|
||||||
network::TransactionBuilder,
|
network::TransactionBuilder,
|
||||||
primitives::{Address, Bytes, U256},
|
primitives::{Address, Bytes, U256},
|
||||||
rpc::types::TransactionRequest,
|
rpc::types::TransactionRequest,
|
||||||
@@ -17,23 +18,87 @@ use revive_dt_common::macros::define_wrapper_type;
|
|||||||
use crate::traits::ResolverApi;
|
use crate::traits::ResolverApi;
|
||||||
use crate::{metadata::ContractInstance, traits::ResolutionContext};
|
use crate::{metadata::ContractInstance, traits::ResolutionContext};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
/// A test step.
|
||||||
|
///
|
||||||
|
/// A test step can be anything. It could be an invocation to a function, an assertion, or any other
|
||||||
|
/// action that needs to be run or executed on the nodes used in the tests.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum Step {
|
||||||
|
/// A function call or an invocation to some function on some smart contract.
|
||||||
|
FunctionCall(Box<Input>),
|
||||||
|
/// A step for performing a balance assertion on some account or contract.
|
||||||
|
BalanceAssertion(Box<BalanceAssertion>),
|
||||||
|
/// A step for asserting that the storage of some contract or account is empty.
|
||||||
|
StorageEmptyAssertion(Box<StorageEmptyAssertion>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
#[serde(default = "Input::default_caller")]
|
#[serde(default = "Input::default_caller")]
|
||||||
pub caller: Address,
|
pub caller: Address,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
|
|
||||||
#[serde(default = "Input::default_instance")]
|
#[serde(default = "Input::default_instance")]
|
||||||
pub instance: ContractInstance,
|
pub instance: ContractInstance,
|
||||||
|
|
||||||
pub method: Method,
|
pub method: Method,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub calldata: Calldata,
|
pub calldata: Calldata,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub expected: Option<Expected>,
|
pub expected: Option<Expected>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub value: Option<EtherValue>,
|
pub value: Option<EtherValue>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub storage: Option<HashMap<String, Calldata>>,
|
pub storage: Option<HashMap<String, Calldata>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub variable_assignments: Option<VariableAssignments>,
|
pub variable_assignments: Option<VariableAssignments>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
|
pub struct BalanceAssertion {
|
||||||
|
/// An optional comment on the balance assertion.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub comment: Option<String>,
|
||||||
|
|
||||||
|
/// The address that the balance assertion should be done on.
|
||||||
|
///
|
||||||
|
/// This is a string which will be resolved into an address when being processed. Therefore,
|
||||||
|
/// 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
|
||||||
|
/// followed in the calldata.
|
||||||
|
pub address: String,
|
||||||
|
|
||||||
|
/// The amount of balance to assert that the account or contract has.
|
||||||
|
pub expected_balance: U256,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
|
pub struct StorageEmptyAssertion {
|
||||||
|
/// An optional comment on the storage empty assertion.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub comment: Option<String>,
|
||||||
|
|
||||||
|
/// The address that the balance assertion should be done on.
|
||||||
|
///
|
||||||
|
/// This is a string which will be resolved into an address when being processed. Therefore,
|
||||||
|
/// 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
|
||||||
|
/// followed in the calldata.
|
||||||
|
pub address: String,
|
||||||
|
|
||||||
|
/// A boolean of whether the storage of the address is empty or not.
|
||||||
|
pub is_storage_empty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Expected {
|
pub enum Expected {
|
||||||
Calldata(Calldata),
|
Calldata(Calldata),
|
||||||
@@ -41,17 +106,21 @@ pub enum Expected {
|
|||||||
ExpectedMany(Vec<ExpectedOutput>),
|
ExpectedMany(Vec<ExpectedOutput>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
pub struct ExpectedOutput {
|
pub struct ExpectedOutput {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub compiler_version: Option<VersionReq>,
|
pub compiler_version: Option<VersionReq>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub return_data: Option<Calldata>,
|
pub return_data: Option<Calldata>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub events: Option<Vec<Event>>,
|
pub events: Option<Vec<Event>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub exception: bool,
|
pub exception: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
pub struct Event {
|
pub struct Event {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub address: Option<String>,
|
pub address: Option<String>,
|
||||||
pub topics: Vec<String>,
|
pub topics: Vec<String>,
|
||||||
pub values: Calldata,
|
pub values: Calldata,
|
||||||
@@ -108,7 +177,7 @@ pub struct Event {
|
|||||||
/// [`Single`]: Calldata::Single
|
/// [`Single`]: Calldata::Single
|
||||||
/// [`Compound`]: Calldata::Compound
|
/// [`Compound`]: Calldata::Compound
|
||||||
/// [reverse polish notation]: https://en.wikipedia.org/wiki/Reverse_Polish_notation
|
/// [reverse polish notation]: https://en.wikipedia.org/wiki/Reverse_Polish_notation
|
||||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Calldata {
|
pub enum Calldata {
|
||||||
Single(Bytes),
|
Single(Bytes),
|
||||||
@@ -142,7 +211,7 @@ enum Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Specify how the contract is called.
|
/// Specify how the contract is called.
|
||||||
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
|
||||||
pub enum Method {
|
pub enum Method {
|
||||||
/// Initiate a deploy transaction, calling contracts constructor.
|
/// Initiate a deploy transaction, calling contracts constructor.
|
||||||
///
|
///
|
||||||
@@ -167,7 +236,7 @@ define_wrapper_type!(
|
|||||||
pub struct EtherValue(U256);
|
pub struct EtherValue(U256);
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
pub struct VariableAssignments {
|
pub struct VariableAssignments {
|
||||||
/// A vector of the variable names to assign to the return data.
|
/// A vector of the variable names to assign to the return data.
|
||||||
///
|
///
|
||||||
@@ -211,9 +280,17 @@ impl Input {
|
|||||||
// We follow the same logic that's implemented in the matter-labs-tester where they resolve
|
// We follow the same logic that's implemented in the matter-labs-tester where they resolve
|
||||||
// the function name into a function selector and they assume that he function doesn't have
|
// the function name into a function selector and they assume that he function doesn't have
|
||||||
// any existing overloads.
|
// any existing overloads.
|
||||||
|
// Overloads are handled by providing the full function signature in the "function
|
||||||
|
// name".
|
||||||
// https://github.com/matter-labs/era-compiler-tester/blob/1dfa7d07cba0734ca97e24704f12dd57f6990c2c/compiler_tester/src/test/case/input/mod.rs#L158-L190
|
// https://github.com/matter-labs/era-compiler-tester/blob/1dfa7d07cba0734ca97e24704f12dd57f6990c2c/compiler_tester/src/test/case/input/mod.rs#L158-L190
|
||||||
let function = abi
|
let selector = if function_name.contains('(') && function_name.contains(')') {
|
||||||
.functions()
|
Function::parse(function_name)
|
||||||
|
.context(
|
||||||
|
"Failed to parse the provided function name into a function signature",
|
||||||
|
)?
|
||||||
|
.selector()
|
||||||
|
} else {
|
||||||
|
abi.functions()
|
||||||
.find(|function| function.signature().starts_with(function_name))
|
.find(|function| function.signature().starts_with(function_name))
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
anyhow::anyhow!(
|
anyhow::anyhow!(
|
||||||
@@ -221,7 +298,9 @@ impl Input {
|
|||||||
function_name,
|
function_name,
|
||||||
&self.instance
|
&self.instance
|
||||||
)
|
)
|
||||||
})?;
|
})?
|
||||||
|
.selector()
|
||||||
|
};
|
||||||
|
|
||||||
tracing::trace!("Functions found for instance: {}", self.instance.as_ref());
|
tracing::trace!("Functions found for instance: {}", self.instance.as_ref());
|
||||||
|
|
||||||
@@ -237,7 +316,7 @@ impl Input {
|
|||||||
// We're using indices in the following code in order to avoid the need for us to allocate
|
// We're using indices in the following code in order to avoid the need for us to allocate
|
||||||
// a new buffer for each one of the resolved arguments.
|
// a new buffer for each one of the resolved arguments.
|
||||||
let mut calldata = Vec::<u8>::with_capacity(4 + self.calldata.size_requirement());
|
let mut calldata = Vec::<u8>::with_capacity(4 + self.calldata.size_requirement());
|
||||||
calldata.extend(function.selector().0);
|
calldata.extend(selector.0);
|
||||||
self.calldata
|
self.calldata
|
||||||
.calldata_into_slice(&mut calldata, resolver, context)
|
.calldata_into_slice(&mut calldata, resolver, context)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -599,7 +678,7 @@ impl<T: AsRef<str>> CalldataToken<T> {
|
|||||||
Some(block_number) => *block_number,
|
Some(block_number) => *block_number,
|
||||||
None => resolver.last_block_number().await?,
|
None => resolver.last_block_number().await?,
|
||||||
};
|
};
|
||||||
let desired_block_number = current_block_number - offset;
|
let desired_block_number = current_block_number.saturating_sub(offset);
|
||||||
|
|
||||||
let block_hash = resolver.block_hash(desired_block_number.into()).await?;
|
let block_hash = resolver.block_hash(desired_block_number.into()).await?;
|
||||||
|
|
||||||
@@ -612,7 +691,7 @@ impl<T: AsRef<str>> CalldataToken<T> {
|
|||||||
Ok(U256::from(current_block_number))
|
Ok(U256::from(current_block_number))
|
||||||
} else if item == Self::BLOCK_TIMESTAMP_VARIABLE {
|
} else if item == Self::BLOCK_TIMESTAMP_VARIABLE {
|
||||||
resolver
|
resolver
|
||||||
.block_timestamp(BlockNumberOrTag::Latest)
|
.block_timestamp(context.resolve_block_number(BlockNumberOrTag::Latest))
|
||||||
.await
|
.await
|
||||||
.map(U256::from)
|
.map(U256::from)
|
||||||
} else if let Some(variable_name) = item.strip_prefix(Self::VARIABLE_PREFIX) {
|
} else if let Some(variable_name) = item.strip_prefix(Self::VARIABLE_PREFIX) {
|
||||||
@@ -660,12 +739,14 @@ impl<'de> Deserialize<'de> for EtherValue {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::*;
|
|
||||||
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;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::metadata::ContractIdent;
|
||||||
|
|
||||||
struct MockResolver;
|
struct MockResolver;
|
||||||
|
|
||||||
impl ResolverApi for MockResolver {
|
impl ResolverApi for MockResolver {
|
||||||
@@ -739,11 +820,11 @@ mod tests {
|
|||||||
let mut contracts = HashMap::new();
|
let mut contracts = HashMap::new();
|
||||||
contracts.insert(
|
contracts.insert(
|
||||||
ContractInstance::new("Contract"),
|
ContractInstance::new("Contract"),
|
||||||
(Address::ZERO, parsed_abi),
|
(ContractIdent::new("Contract"), Address::ZERO, parsed_abi),
|
||||||
);
|
);
|
||||||
|
|
||||||
let resolver = MockResolver;
|
let resolver = MockResolver;
|
||||||
let context = ResolutionContext::new_from_parts(&contracts, None, None, None);
|
let context = ResolutionContext::default().with_deployed_contracts(&contracts);
|
||||||
let encoded = input.encoded_input(&resolver, context).await.unwrap();
|
let encoded = input.encoded_input(&resolver, context).await.unwrap();
|
||||||
assert!(encoded.0.starts_with(&selector));
|
assert!(encoded.0.starts_with(&selector));
|
||||||
|
|
||||||
@@ -783,11 +864,11 @@ mod tests {
|
|||||||
let mut contracts = HashMap::new();
|
let mut contracts = HashMap::new();
|
||||||
contracts.insert(
|
contracts.insert(
|
||||||
ContractInstance::new("Contract"),
|
ContractInstance::new("Contract"),
|
||||||
(Address::ZERO, parsed_abi),
|
(ContractIdent::new("Contract"), Address::ZERO, parsed_abi),
|
||||||
);
|
);
|
||||||
|
|
||||||
let resolver = MockResolver;
|
let resolver = MockResolver;
|
||||||
let context = ResolutionContext::new_from_parts(&contracts, None, None, None);
|
let context = ResolutionContext::default().with_deployed_contracts(&contracts);
|
||||||
let encoded = input.encoded_input(&resolver, context).await.unwrap();
|
let encoded = input.encoded_input(&resolver, context).await.unwrap();
|
||||||
assert!(encoded.0.starts_with(&selector));
|
assert!(encoded.0.starts_with(&selector));
|
||||||
|
|
||||||
@@ -830,11 +911,11 @@ mod tests {
|
|||||||
let mut contracts = HashMap::new();
|
let mut contracts = HashMap::new();
|
||||||
contracts.insert(
|
contracts.insert(
|
||||||
ContractInstance::new("Contract"),
|
ContractInstance::new("Contract"),
|
||||||
(Address::ZERO, parsed_abi),
|
(ContractIdent::new("Contract"), Address::ZERO, parsed_abi),
|
||||||
);
|
);
|
||||||
|
|
||||||
let resolver = MockResolver;
|
let resolver = MockResolver;
|
||||||
let context = ResolutionContext::new_from_parts(&contracts, None, None, None);
|
let context = ResolutionContext::default().with_deployed_contracts(&contracts);
|
||||||
let encoded = input.encoded_input(&resolver, context).await.unwrap();
|
let encoded = input.encoded_input(&resolver, context).await.unwrap();
|
||||||
assert!(encoded.0.starts_with(&selector));
|
assert!(encoded.0.starts_with(&selector));
|
||||||
|
|
||||||
@@ -848,10 +929,10 @@ mod tests {
|
|||||||
|
|
||||||
async fn resolve_calldata_item(
|
async fn resolve_calldata_item(
|
||||||
input: &str,
|
input: &str,
|
||||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
deployed_contracts: &HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>,
|
||||||
resolver: &impl ResolverApi,
|
resolver: &impl ResolverApi,
|
||||||
) -> anyhow::Result<U256> {
|
) -> anyhow::Result<U256> {
|
||||||
let context = ResolutionContext::new_from_parts(deployed_contracts, None, None, None);
|
let context = ResolutionContext::default().with_deployed_contracts(deployed_contracts);
|
||||||
CalldataItem::new(input).resolve(resolver, context).await
|
CalldataItem::new(input).resolve(resolver, context).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+199
-28
@@ -1,7 +1,8 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
fs::{File, read_to_string},
|
fs::File,
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
@@ -9,13 +10,14 @@ use std::{
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use revive_dt_common::{iterators::FilesWithExtensionIterator, macros::define_wrapper_type};
|
use revive_common::EVMVersion;
|
||||||
|
use revive_dt_common::{
|
||||||
use crate::{
|
cached_fs::read_to_string, iterators::FilesWithExtensionIterator, macros::define_wrapper_type,
|
||||||
case::Case,
|
types::Mode,
|
||||||
mode::{Mode, SolcMode},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{case::Case, mode::ParsedMode};
|
||||||
|
|
||||||
pub const METADATA_FILE_EXTENSION: &str = "json";
|
pub const METADATA_FILE_EXTENSION: &str = "json";
|
||||||
pub const SOLIDITY_CASE_FILE_EXTENSION: &str = "sol";
|
pub const SOLIDITY_CASE_FILE_EXTENSION: &str = "sol";
|
||||||
pub const SOLIDITY_CASE_COMMENT_MARKER: &str = "//!";
|
pub const SOLIDITY_CASE_COMMENT_MARKER: &str = "//!";
|
||||||
@@ -43,34 +45,51 @@ impl Deref for MetadataFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
pub targets: Option<Vec<String>>,
|
/// A comment on the test case that's added for human-readability.
|
||||||
pub cases: Vec<Case>,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub contracts: Option<BTreeMap<ContractInstance, ContractPathAndIdent>>,
|
pub comment: Option<String>,
|
||||||
// TODO: Convert into wrapper types for clarity.
|
|
||||||
pub libraries: Option<BTreeMap<PathBuf, BTreeMap<ContractIdent, ContractInstance>>>,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub ignore: Option<bool>,
|
pub ignore: Option<bool>,
|
||||||
pub modes: Option<Vec<Mode>>,
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub targets: Option<Vec<String>>,
|
||||||
|
|
||||||
|
pub cases: Vec<Case>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub contracts: Option<BTreeMap<ContractInstance, ContractPathAndIdent>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub libraries: Option<BTreeMap<PathBuf, BTreeMap<ContractIdent, ContractInstance>>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub modes: Option<Vec<ParsedMode>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub file_path: Option<PathBuf>,
|
pub file_path: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// This field specifies an EVM version requirement that the test case has where the test might
|
||||||
|
/// be run of the evm version of the nodes match the evm version specified here.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub required_evm_version: Option<EvmVersionRequirement>,
|
||||||
|
|
||||||
|
/// A set of compilation directives that will be passed to the compiler whenever the contracts for
|
||||||
|
/// the test are being compiled. Note that this differs from the [`Mode`]s in that a [`Mode`] is
|
||||||
|
/// just a filter for when a test can run whereas this is an instruction to the compiler.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub compiler_directives: Option<CompilationDirectives>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Metadata {
|
impl Metadata {
|
||||||
/// Returns the solc modes of this metadata, inserting a default mode if not present.
|
/// Returns the modes that we should test from this metadata.
|
||||||
pub fn solc_modes(&self) -> Vec<SolcMode> {
|
pub fn solc_modes(&self) -> Vec<Mode> {
|
||||||
self.modes
|
match &self.modes {
|
||||||
.to_owned()
|
Some(modes) => ParsedMode::many_to_modes(modes.iter()).collect(),
|
||||||
.unwrap_or_else(|| vec![Mode::Solidity(Default::default())])
|
None => Mode::all().collect(),
|
||||||
.iter()
|
|
||||||
.filter_map(|mode| match mode {
|
|
||||||
Mode::Solidity(solc_mode) => Some(solc_mode),
|
|
||||||
Mode::Unknown(mode) => {
|
|
||||||
tracing::debug!("compiler: ignoring unknown mode '{mode}'");
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.cloned()
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the base directory of this metadata.
|
/// Returns the base directory of this metadata.
|
||||||
@@ -231,7 +250,9 @@ impl Metadata {
|
|||||||
Ok(Box::new(std::iter::once(metadata_file_path.clone())))
|
Ok(Box::new(std::iter::once(metadata_file_path.clone())))
|
||||||
} else {
|
} else {
|
||||||
Ok(Box::new(
|
Ok(Box::new(
|
||||||
FilesWithExtensionIterator::new(self.directory()?).with_allowed_extension("sol"),
|
FilesWithExtensionIterator::new(self.directory()?)
|
||||||
|
.with_allowed_extension("sol")
|
||||||
|
.with_use_cached_fs(true),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,6 +364,156 @@ impl From<ContractPathAndIdent> for String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An EVM version requirement that the test case has. This gets serialized and
|
||||||
|
/// deserialized from and into [`String`].
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
|
#[serde(try_from = "String", into = "String")]
|
||||||
|
pub struct EvmVersionRequirement {
|
||||||
|
ordering: Ordering,
|
||||||
|
or_equal: bool,
|
||||||
|
evm_version: EVMVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EvmVersionRequirement {
|
||||||
|
pub fn new_greater_than_or_equals(version: EVMVersion) -> Self {
|
||||||
|
Self {
|
||||||
|
ordering: Ordering::Greater,
|
||||||
|
or_equal: true,
|
||||||
|
evm_version: version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_greater_than(version: EVMVersion) -> Self {
|
||||||
|
Self {
|
||||||
|
ordering: Ordering::Greater,
|
||||||
|
or_equal: false,
|
||||||
|
evm_version: version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_equals(version: EVMVersion) -> Self {
|
||||||
|
Self {
|
||||||
|
ordering: Ordering::Equal,
|
||||||
|
or_equal: false,
|
||||||
|
evm_version: version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_less_than(version: EVMVersion) -> Self {
|
||||||
|
Self {
|
||||||
|
ordering: Ordering::Less,
|
||||||
|
or_equal: false,
|
||||||
|
evm_version: version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_less_than_or_equals(version: EVMVersion) -> Self {
|
||||||
|
Self {
|
||||||
|
ordering: Ordering::Less,
|
||||||
|
or_equal: true,
|
||||||
|
evm_version: version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matches(&self, other: &EVMVersion) -> bool {
|
||||||
|
let ordering = other.cmp(&self.evm_version);
|
||||||
|
ordering == self.ordering || (self.or_equal && matches!(ordering, Ordering::Equal))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for EvmVersionRequirement {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let Self {
|
||||||
|
ordering,
|
||||||
|
or_equal,
|
||||||
|
evm_version,
|
||||||
|
} = self;
|
||||||
|
match ordering {
|
||||||
|
Ordering::Less => write!(f, "<")?,
|
||||||
|
Ordering::Equal => write!(f, "=")?,
|
||||||
|
Ordering::Greater => write!(f, ">")?,
|
||||||
|
}
|
||||||
|
if *or_equal && !matches!(ordering, Ordering::Equal) {
|
||||||
|
write!(f, "=")?;
|
||||||
|
}
|
||||||
|
write!(f, "{evm_version}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for EvmVersionRequirement {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s.as_bytes() {
|
||||||
|
[b'>', b'=', remaining @ ..] => Ok(Self {
|
||||||
|
ordering: Ordering::Greater,
|
||||||
|
or_equal: true,
|
||||||
|
evm_version: str::from_utf8(remaining)?.try_into()?,
|
||||||
|
}),
|
||||||
|
[b'>', remaining @ ..] => Ok(Self {
|
||||||
|
ordering: Ordering::Greater,
|
||||||
|
or_equal: false,
|
||||||
|
evm_version: str::from_utf8(remaining)?.try_into()?,
|
||||||
|
}),
|
||||||
|
[b'<', b'=', remaining @ ..] => Ok(Self {
|
||||||
|
ordering: Ordering::Less,
|
||||||
|
or_equal: true,
|
||||||
|
evm_version: str::from_utf8(remaining)?.try_into()?,
|
||||||
|
}),
|
||||||
|
[b'<', remaining @ ..] => Ok(Self {
|
||||||
|
ordering: Ordering::Less,
|
||||||
|
or_equal: false,
|
||||||
|
evm_version: str::from_utf8(remaining)?.try_into()?,
|
||||||
|
}),
|
||||||
|
[b'=', remaining @ ..] => Ok(Self {
|
||||||
|
ordering: Ordering::Equal,
|
||||||
|
or_equal: false,
|
||||||
|
evm_version: str::from_utf8(remaining)?.try_into()?,
|
||||||
|
}),
|
||||||
|
_ => anyhow::bail!("Invalid EVM version requirement {s}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for EvmVersionRequirement {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
value.parse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EvmVersionRequirement> for String {
|
||||||
|
fn from(value: EvmVersionRequirement) -> Self {
|
||||||
|
value.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set of compilation directives that will be passed to the compiler whenever the contracts for
|
||||||
|
/// the test are being compiled. Note that this differs from the [`Mode`]s in that a [`Mode`] is
|
||||||
|
/// just a filter for when a test can run whereas this is an instruction to the compiler.
|
||||||
|
/// Defines how the compiler should handle revert strings.
|
||||||
|
#[derive(
|
||||||
|
Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
|
pub struct CompilationDirectives {
|
||||||
|
/// Defines how the revert strings should be handled.
|
||||||
|
pub revert_string_handling: Option<RevertString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines how the compiler should handle revert strings.
|
||||||
|
#[derive(
|
||||||
|
Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub enum RevertString {
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
Debug,
|
||||||
|
Strip,
|
||||||
|
VerboseDebug,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
+238
-82
@@ -1,106 +1,262 @@
|
|||||||
use revive_dt_common::types::VersionOrRequirement;
|
use regex::Regex;
|
||||||
use semver::Version;
|
use revive_dt_common::types::{Mode, ModeOptimizerSetting, ModePipeline};
|
||||||
use serde::de::Deserializer;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
/// Specifies the compilation mode of the test artifact.
|
/// This represents a mode that has been parsed from test metadata.
|
||||||
#[derive(Hash, Debug, Clone, Eq, PartialEq)]
|
|
||||||
pub enum Mode {
|
|
||||||
Solidity(SolcMode),
|
|
||||||
Unknown(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Specify Solidity specific compiler options.
|
|
||||||
#[derive(Hash, Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct SolcMode {
|
|
||||||
pub solc_version: Option<semver::VersionReq>,
|
|
||||||
solc_optimize: Option<bool>,
|
|
||||||
pub llvm_optimizer_settings: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SolcMode {
|
|
||||||
/// Try to parse a mode string into a solc mode.
|
|
||||||
/// Returns `None` if the string wasn't a solc YUL mode string.
|
|
||||||
///
|
///
|
||||||
/// The mode string is expected to start with the `Y` ID (YUL ID),
|
/// Mode strings can take the following form (in pseudo-regex):
|
||||||
/// optionally followed by `+` or `-` for the solc optimizer settings.
|
|
||||||
///
|
///
|
||||||
/// Options can be separated by a whitespace contain the following
|
/// ```text
|
||||||
/// - A solc `SemVer version requirement` string
|
/// [YEILV][+-]? (M[0123sz])? <semver>?
|
||||||
/// - One or more `-OX` where X is a supposed to be an LLVM opt mode
|
/// ```
|
||||||
pub fn parse_from_mode_string(mode_string: &str) -> Option<Self> {
|
///
|
||||||
let mut result = Self::default();
|
/// We can parse valid mode strings into [`ParsedMode`] using [`ParsedMode::from_str`].
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
let mut parts = mode_string.trim().split(" ");
|
#[serde(try_from = "String", into = "String")]
|
||||||
|
pub struct ParsedMode {
|
||||||
match parts.next()? {
|
pub pipeline: Option<ModePipeline>,
|
||||||
"Y" => {}
|
pub optimize_flag: Option<bool>,
|
||||||
"Y+" => result.solc_optimize = Some(true),
|
pub optimize_setting: Option<ModeOptimizerSetting>,
|
||||||
"Y-" => result.solc_optimize = Some(false),
|
pub version: Option<semver::VersionReq>,
|
||||||
_ => return None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for part in parts {
|
impl FromStr for ParsedMode {
|
||||||
if let Ok(solc_version) = semver::VersionReq::parse(part) {
|
type Err = anyhow::Error;
|
||||||
result.solc_version = Some(solc_version);
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
continue;
|
static REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||||
}
|
Regex::new(r"(?x)
|
||||||
if let Some(level) = part.strip_prefix("-O") {
|
^
|
||||||
result.llvm_optimizer_settings.push(level.to_string());
|
(?:(?P<pipeline>[YEILV])(?P<optimize_flag>[+-])?)? # Pipeline to use eg Y, E+, E-
|
||||||
continue;
|
\s*
|
||||||
}
|
(?P<optimize_setting>M[a-zA-Z0-9])? # Optimize setting eg M0, Ms, Mz
|
||||||
panic!("the YUL mode string {mode_string} failed to parse, invalid part: {part}")
|
\s*
|
||||||
}
|
(?P<version>[>=<]*\d+(?:\.\d+)*)? # Optional semver version eg >=0.8.0, 0.7, <0.8
|
||||||
|
$
|
||||||
|
").unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
Some(result)
|
let Some(caps) = REGEX.captures(s) else {
|
||||||
}
|
anyhow::bail!("Cannot parse mode '{s}' from string");
|
||||||
|
|
||||||
/// Returns whether to enable the solc optimizer.
|
|
||||||
pub fn solc_optimize(&self) -> bool {
|
|
||||||
self.solc_optimize.unwrap_or(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate the latest matching solc patch version. Returns:
|
|
||||||
/// - `latest_supported` if no version request was specified.
|
|
||||||
/// - A matching version with the same minor version as `latest_supported`, if any.
|
|
||||||
/// - `None` if no minor version of the `latest_supported` version matches.
|
|
||||||
pub fn last_patch_version(&self, latest_supported: &Version) -> Option<Version> {
|
|
||||||
let Some(version_req) = self.solc_version.as_ref() else {
|
|
||||||
return Some(latest_supported.to_owned());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// lgtm
|
let pipeline = match caps.name("pipeline") {
|
||||||
for patch in (0..latest_supported.patch + 1).rev() {
|
Some(m) => Some(ModePipeline::from_str(m.as_str())?),
|
||||||
let version = Version::new(0, latest_supported.minor, patch);
|
None => None,
|
||||||
if version_req.matches(&version) {
|
};
|
||||||
return Some(version);
|
|
||||||
|
let optimize_flag = caps.name("optimize_flag").map(|m| m.as_str() == "+");
|
||||||
|
|
||||||
|
let optimize_setting = match caps.name("optimize_setting") {
|
||||||
|
Some(m) => Some(ModeOptimizerSetting::from_str(m.as_str())?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let version = match caps.name("version") {
|
||||||
|
Some(m) => Some(semver::VersionReq::parse(m.as_str()).map_err(|e| {
|
||||||
|
anyhow::anyhow!("Cannot parse the version requirement '{}': {e}", m.as_str())
|
||||||
|
})?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ParsedMode {
|
||||||
|
pipeline,
|
||||||
|
optimize_flag,
|
||||||
|
optimize_setting,
|
||||||
|
version,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
impl Display for ParsedMode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut has_written = false;
|
||||||
|
|
||||||
|
if let Some(pipeline) = self.pipeline {
|
||||||
|
pipeline.fmt(f)?;
|
||||||
|
if let Some(optimize_flag) = self.optimize_flag {
|
||||||
|
f.write_str(if optimize_flag { "+" } else { "-" })?;
|
||||||
|
}
|
||||||
|
has_written = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves the [`SolcMode`]'s solidity version requirement into a [`VersionOrRequirement`] if
|
if let Some(optimize_setting) = self.optimize_setting {
|
||||||
/// the requirement is present on the object. Otherwise, the passed default version is used.
|
if has_written {
|
||||||
pub fn compiler_version_to_use(&self, default: Version) -> VersionOrRequirement {
|
f.write_str(" ")?;
|
||||||
match self.solc_version {
|
|
||||||
Some(ref requirement) => requirement.clone().into(),
|
|
||||||
None => default.into(),
|
|
||||||
}
|
}
|
||||||
|
optimize_setting.fmt(f)?;
|
||||||
|
has_written = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(version) = &self.version {
|
||||||
|
if has_written {
|
||||||
|
f.write_str(" ")?;
|
||||||
|
}
|
||||||
|
version.fmt(f)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Mode {
|
impl From<ParsedMode> for String {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn from(parsed_mode: ParsedMode) -> Self {
|
||||||
|
parsed_mode.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for ParsedMode {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
ParsedMode::from_str(&value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParsedMode {
|
||||||
|
/// This takes a [`ParsedMode`] and expands it into a list of [`Mode`]s that we should try.
|
||||||
|
pub fn to_modes(&self) -> impl Iterator<Item = Mode> {
|
||||||
|
let pipeline_iter = self.pipeline.as_ref().map_or_else(
|
||||||
|
|| EitherIter::A(ModePipeline::test_cases()),
|
||||||
|
|p| EitherIter::B(std::iter::once(*p)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let optimize_flag_setting = self.optimize_flag.map(|flag| {
|
||||||
|
if flag {
|
||||||
|
ModeOptimizerSetting::M3
|
||||||
|
} else {
|
||||||
|
ModeOptimizerSetting::M0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let optimize_flag_iter = match optimize_flag_setting {
|
||||||
|
Some(setting) => EitherIter::A(std::iter::once(setting)),
|
||||||
|
None => EitherIter::B(ModeOptimizerSetting::test_cases()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let optimize_settings_iter = self.optimize_setting.as_ref().map_or_else(
|
||||||
|
|| EitherIter::A(optimize_flag_iter),
|
||||||
|
|s| EitherIter::B(std::iter::once(*s)),
|
||||||
|
);
|
||||||
|
|
||||||
|
pipeline_iter.flat_map(move |pipeline| {
|
||||||
|
optimize_settings_iter
|
||||||
|
.clone()
|
||||||
|
.map(move |optimize_setting| Mode {
|
||||||
|
pipeline,
|
||||||
|
optimize_setting,
|
||||||
|
version: self.version.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a set of [`Mode`]s that correspond to the given [`ParsedMode`]s.
|
||||||
|
/// This avoids any duplicate entries.
|
||||||
|
pub fn many_to_modes<'a>(
|
||||||
|
parsed: impl Iterator<Item = &'a ParsedMode>,
|
||||||
|
) -> impl Iterator<Item = Mode> {
|
||||||
|
let modes: HashSet<_> = parsed.flat_map(|p| p.to_modes()).collect();
|
||||||
|
modes.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator that could be either of two iterators.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum EitherIter<A, B> {
|
||||||
|
A(A),
|
||||||
|
B(B),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, B> Iterator for EitherIter<A, B>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
A: Iterator,
|
||||||
|
B: Iterator<Item = A::Item>,
|
||||||
{
|
{
|
||||||
let mode_string = String::deserialize(deserializer)?;
|
type Item = A::Item;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if let Some(solc_mode) = SolcMode::parse_from_mode_string(&mode_string) {
|
match self {
|
||||||
return Ok(Self::Solidity(solc_mode));
|
EitherIter::A(iter) => iter.next(),
|
||||||
|
EitherIter::B(iter) => iter.next(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self::Unknown(mode_string))
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parsed_mode_from_str() {
|
||||||
|
let strings = vec![
|
||||||
|
("Mz", "Mz"),
|
||||||
|
("Y", "Y"),
|
||||||
|
("Y+", "Y+"),
|
||||||
|
("Y-", "Y-"),
|
||||||
|
("E", "E"),
|
||||||
|
("E+", "E+"),
|
||||||
|
("E-", "E-"),
|
||||||
|
("Y M0", "Y M0"),
|
||||||
|
("Y M1", "Y M1"),
|
||||||
|
("Y M2", "Y M2"),
|
||||||
|
("Y M3", "Y M3"),
|
||||||
|
("Y Ms", "Y Ms"),
|
||||||
|
("Y Mz", "Y Mz"),
|
||||||
|
("E M0", "E M0"),
|
||||||
|
("E M1", "E M1"),
|
||||||
|
("E M2", "E M2"),
|
||||||
|
("E M3", "E M3"),
|
||||||
|
("E Ms", "E Ms"),
|
||||||
|
("E Mz", "E Mz"),
|
||||||
|
// When stringifying semver again, 0.8.0 becomes ^0.8.0 (same meaning)
|
||||||
|
("Y 0.8.0", "Y ^0.8.0"),
|
||||||
|
("E+ 0.8.0", "E+ ^0.8.0"),
|
||||||
|
("Y M3 >=0.8.0", "Y M3 >=0.8.0"),
|
||||||
|
("E Mz <0.7.0", "E Mz <0.7.0"),
|
||||||
|
// We can parse +- _and_ M1/M2 but the latter takes priority.
|
||||||
|
("Y+ M1 0.8.0", "Y+ M1 ^0.8.0"),
|
||||||
|
("E- M2 0.7.0", "E- M2 ^0.7.0"),
|
||||||
|
// We don't see this in the wild but it is parsed.
|
||||||
|
("<=0.8", "<=0.8"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (actual, expected) in strings {
|
||||||
|
let parsed = ParsedMode::from_str(actual)
|
||||||
|
.expect(format!("Failed to parse mode string '{actual}'").as_str());
|
||||||
|
assert_eq!(
|
||||||
|
expected,
|
||||||
|
parsed.to_string(),
|
||||||
|
"Mode string '{actual}' did not parse to '{expected}': got '{parsed}'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parsed_mode_to_test_modes() {
|
||||||
|
let strings = vec![
|
||||||
|
("Mz", vec!["Y Mz", "E Mz"]),
|
||||||
|
("Y", vec!["Y M0", "Y M3"]),
|
||||||
|
("E", vec!["E M0", "E M3"]),
|
||||||
|
("Y+", vec!["Y M3"]),
|
||||||
|
("Y-", vec!["Y M0"]),
|
||||||
|
("Y <=0.8", vec!["Y M0 <=0.8", "Y M3 <=0.8"]),
|
||||||
|
(
|
||||||
|
"<=0.8",
|
||||||
|
vec!["Y M0 <=0.8", "Y M3 <=0.8", "E M0 <=0.8", "E M3 <=0.8"],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (actual, expected) in strings {
|
||||||
|
let parsed = ParsedMode::from_str(actual)
|
||||||
|
.expect(format!("Failed to parse mode string '{actual}'").as_str());
|
||||||
|
let expected_set: HashSet<_> = expected.into_iter().map(|s| s.to_owned()).collect();
|
||||||
|
let actual_set: HashSet<_> = parsed.to_modes().map(|m| m.to_string()).collect();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
expected_set, actual_set,
|
||||||
|
"Mode string '{actual}' did not expand to '{expected_set:?}': got '{actual_set:?}'"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId
|
|||||||
use alloy_primitives::TxHash;
|
use alloy_primitives::TxHash;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::metadata::ContractInstance;
|
use crate::metadata::{ContractIdent, ContractInstance};
|
||||||
|
|
||||||
/// A trait of the interface are required to implement to be used by the resolution logic that this
|
/// A trait of the interface are required to implement to be used by the resolution logic that this
|
||||||
/// crate implements to go from string calldata and into the bytes calldata.
|
/// crate implements to go from string calldata and into the bytes calldata.
|
||||||
@@ -48,7 +48,7 @@ pub trait ResolverApi {
|
|||||||
/// Contextual information required by the code that's performing the resolution.
|
/// Contextual information required by the code that's performing the resolution.
|
||||||
pub struct ResolutionContext<'a> {
|
pub struct ResolutionContext<'a> {
|
||||||
/// When provided the contracts provided here will be used for resolutions.
|
/// When provided the contracts provided here will be used for resolutions.
|
||||||
deployed_contracts: Option<&'a HashMap<ContractInstance, (Address, JsonAbi)>>,
|
deployed_contracts: Option<&'a HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
|
||||||
|
|
||||||
/// When provided the variables in here will be used for performing resolutions.
|
/// When provided the variables in here will be used for performing resolutions.
|
||||||
variables: Option<&'a HashMap<String, U256>>,
|
variables: Option<&'a HashMap<String, U256>>,
|
||||||
@@ -66,7 +66,9 @@ impl<'a> ResolutionContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_from_parts(
|
pub fn new_from_parts(
|
||||||
deployed_contracts: impl Into<Option<&'a HashMap<ContractInstance, (Address, JsonAbi)>>>,
|
deployed_contracts: impl Into<
|
||||||
|
Option<&'a HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
|
||||||
|
>,
|
||||||
variables: impl Into<Option<&'a HashMap<String, U256>>>,
|
variables: impl Into<Option<&'a HashMap<String, U256>>>,
|
||||||
block_number: impl Into<Option<&'a BlockNumber>>,
|
block_number: impl Into<Option<&'a BlockNumber>>,
|
||||||
transaction_hash: impl Into<Option<&'a TxHash>>,
|
transaction_hash: impl Into<Option<&'a TxHash>>,
|
||||||
@@ -81,7 +83,9 @@ impl<'a> ResolutionContext<'a> {
|
|||||||
|
|
||||||
pub fn with_deployed_contracts(
|
pub fn with_deployed_contracts(
|
||||||
mut self,
|
mut self,
|
||||||
deployed_contracts: impl Into<Option<&'a HashMap<ContractInstance, (Address, JsonAbi)>>>,
|
deployed_contracts: impl Into<
|
||||||
|
Option<&'a HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
|
||||||
|
>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.deployed_contracts = deployed_contracts.into();
|
self.deployed_contracts = deployed_contracts.into();
|
||||||
self
|
self
|
||||||
@@ -122,17 +126,20 @@ impl<'a> ResolutionContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deployed_contract(&self, instance: &ContractInstance) -> Option<&(Address, JsonAbi)> {
|
pub fn deployed_contract(
|
||||||
|
&self,
|
||||||
|
instance: &ContractInstance,
|
||||||
|
) -> Option<&(ContractIdent, Address, JsonAbi)> {
|
||||||
self.deployed_contracts
|
self.deployed_contracts
|
||||||
.and_then(|deployed_contracts| deployed_contracts.get(instance))
|
.and_then(|deployed_contracts| deployed_contracts.get(instance))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deployed_contract_address(&self, instance: &ContractInstance) -> Option<&Address> {
|
pub fn deployed_contract_address(&self, instance: &ContractInstance) -> Option<&Address> {
|
||||||
self.deployed_contract(instance).map(|(a, _)| a)
|
self.deployed_contract(instance).map(|(_, a, _)| a)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deployed_contract_abi(&self, instance: &ContractInstance) -> Option<&JsonAbi> {
|
pub fn deployed_contract_abi(&self, instance: &ContractInstance) -> Option<&JsonAbi> {
|
||||||
self.deployed_contract(instance).map(|(_, a)| a)
|
self.deployed_contract(instance).map(|(_, _, a)| a)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn variable(&self, name: impl AsRef<str>) -> Option<&U256> {
|
pub fn variable(&self, name: impl AsRef<str>) -> Option<&U256> {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
//! This crate implements all node interactions.
|
//! This crate implements all node interactions.
|
||||||
|
|
||||||
|
use alloy::primitives::{Address, StorageKey, U256};
|
||||||
use alloy::rpc::types::trace::geth::{DiffMode, GethDebugTracingOptions, GethTrace};
|
use alloy::rpc::types::trace::geth::{DiffMode, GethDebugTracingOptions, GethTrace};
|
||||||
use alloy::rpc::types::{TransactionReceipt, TransactionRequest};
|
use alloy::rpc::types::{EIP1186AccountProofResponse, TransactionReceipt, TransactionRequest};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
/// An interface for all interactions with Ethereum compatible nodes.
|
/// An interface for all interactions with Ethereum compatible nodes.
|
||||||
@@ -21,4 +22,14 @@ pub trait EthereumNode {
|
|||||||
|
|
||||||
/// 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, receipt: &TransactionReceipt) -> impl Future<Output = Result<DiffMode>>;
|
||||||
|
|
||||||
|
/// Returns the balance of the provided [`Address`] back.
|
||||||
|
fn balance_of(&self, address: Address) -> impl Future<Output = Result<U256>>;
|
||||||
|
|
||||||
|
/// Returns the latest storage proof of the provided [`Address`]
|
||||||
|
fn latest_state_proof(
|
||||||
|
&self,
|
||||||
|
address: Address,
|
||||||
|
keys: Vec<StorageKey>,
|
||||||
|
) -> impl Future<Output = Result<EIP1186AccountProofResponse>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ alloy = { workspace = true }
|
|||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
||||||
|
revive-common = { workspace = true }
|
||||||
revive-dt-common = { workspace = true }
|
revive-dt-common = { workspace = true }
|
||||||
revive-dt-config = { workspace = true }
|
revive-dt-config = { workspace = true }
|
||||||
revive-dt-format = { workspace = true }
|
revive-dt-format = { workspace = true }
|
||||||
|
|||||||
+65
-34
@@ -17,20 +17,22 @@ use alloy::{
|
|||||||
eips::BlockNumberOrTag,
|
eips::BlockNumberOrTag,
|
||||||
genesis::{Genesis, GenesisAccount},
|
genesis::{Genesis, GenesisAccount},
|
||||||
network::{Ethereum, EthereumWallet, NetworkWallet},
|
network::{Ethereum, EthereumWallet, NetworkWallet},
|
||||||
primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, FixedBytes, TxHash, U256},
|
primitives::{
|
||||||
|
Address, BlockHash, BlockNumber, BlockTimestamp, FixedBytes, StorageKey, TxHash, U256,
|
||||||
|
},
|
||||||
providers::{
|
providers::{
|
||||||
Provider, ProviderBuilder,
|
Provider, ProviderBuilder,
|
||||||
ext::DebugApi,
|
ext::DebugApi,
|
||||||
fillers::{CachedNonceManager, ChainIdFiller, FillProvider, NonceFiller, TxFiller},
|
fillers::{CachedNonceManager, ChainIdFiller, FillProvider, NonceFiller, TxFiller},
|
||||||
layers::CacheLayer,
|
|
||||||
},
|
},
|
||||||
rpc::types::{
|
rpc::types::{
|
||||||
TransactionReceipt, TransactionRequest,
|
EIP1186AccountProofResponse, TransactionReceipt, TransactionRequest,
|
||||||
trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame},
|
trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame},
|
||||||
},
|
},
|
||||||
signers::local::PrivateKeySigner,
|
signers::local::PrivateKeySigner,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use revive_common::EVMVersion;
|
||||||
use tracing::{Instrument, Level};
|
use tracing::{Instrument, Level};
|
||||||
|
|
||||||
use revive_dt_common::{fs::clear_directory, futures::poll};
|
use revive_dt_common::{fs::clear_directory, futures::poll};
|
||||||
@@ -58,7 +60,6 @@ pub struct GethNode {
|
|||||||
geth: PathBuf,
|
geth: PathBuf,
|
||||||
id: u32,
|
id: u32,
|
||||||
handle: Option<Child>,
|
handle: Option<Child>,
|
||||||
network_id: u64,
|
|
||||||
start_timeout: u64,
|
start_timeout: u64,
|
||||||
wallet: EthereumWallet,
|
wallet: EthereumWallet,
|
||||||
nonce_manager: CachedNonceManager,
|
nonce_manager: CachedNonceManager,
|
||||||
@@ -90,7 +91,7 @@ impl GethNode {
|
|||||||
const TRACE_POLLING_DURATION: Duration = Duration::from_secs(60);
|
const TRACE_POLLING_DURATION: Duration = Duration::from_secs(60);
|
||||||
|
|
||||||
/// Create the node directory and call `geth init` to configure the genesis.
|
/// Create the node directory and call `geth init` to configure the genesis.
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn init(&mut self, genesis: String) -> anyhow::Result<&mut Self> {
|
fn init(&mut self, genesis: String) -> anyhow::Result<&mut Self> {
|
||||||
let _ = clear_directory(&self.base_directory);
|
let _ = clear_directory(&self.base_directory);
|
||||||
let _ = clear_directory(&self.logs_directory);
|
let _ = clear_directory(&self.logs_directory);
|
||||||
@@ -140,7 +141,7 @@ impl GethNode {
|
|||||||
/// Spawn the go-ethereum node child process.
|
/// Spawn the go-ethereum node child process.
|
||||||
///
|
///
|
||||||
/// [Instance::init] must be called prior.
|
/// [Instance::init] must be called prior.
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::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
|
// This is the `OpenOptions` that we wish to use for all of the log files that we will be
|
||||||
// opening in this method. We need to construct it in this way to:
|
// opening in this method. We need to construct it in this way to:
|
||||||
@@ -163,8 +164,6 @@ impl GethNode {
|
|||||||
.arg(&self.data_directory)
|
.arg(&self.data_directory)
|
||||||
.arg("--ipcpath")
|
.arg("--ipcpath")
|
||||||
.arg(&self.connection_string)
|
.arg(&self.connection_string)
|
||||||
.arg("--networkid")
|
|
||||||
.arg(self.network_id.to_string())
|
|
||||||
.arg("--nodiscover")
|
.arg("--nodiscover")
|
||||||
.arg("--maxpeers")
|
.arg("--maxpeers")
|
||||||
.arg("0")
|
.arg("0")
|
||||||
@@ -198,7 +197,7 @@ impl GethNode {
|
|||||||
/// Wait for the g-ethereum node child process getting ready.
|
/// Wait for the g-ethereum node child process getting ready.
|
||||||
///
|
///
|
||||||
/// [Instance::spawn_process] must be called priorly.
|
/// [Instance::spawn_process] must be called priorly.
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn wait_ready(&mut self) -> anyhow::Result<&mut Self> {
|
fn wait_ready(&mut self) -> anyhow::Result<&mut Self> {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
@@ -211,6 +210,7 @@ impl GethNode {
|
|||||||
|
|
||||||
let maximum_wait_time = Duration::from_millis(self.start_timeout);
|
let maximum_wait_time = Duration::from_millis(self.start_timeout);
|
||||||
let mut stderr = BufReader::new(logs_file).lines();
|
let mut stderr = BufReader::new(logs_file).lines();
|
||||||
|
let mut lines = vec![];
|
||||||
loop {
|
loop {
|
||||||
if let Some(Ok(line)) = stderr.next() {
|
if let Some(Ok(line)) = stderr.next() {
|
||||||
if line.contains(Self::ERROR_MARKER) {
|
if line.contains(Self::ERROR_MARKER) {
|
||||||
@@ -219,9 +219,14 @@ impl GethNode {
|
|||||||
if line.contains(Self::READY_MARKER) {
|
if line.contains(Self::READY_MARKER) {
|
||||||
return Ok(self);
|
return Ok(self);
|
||||||
}
|
}
|
||||||
|
lines.push(line);
|
||||||
}
|
}
|
||||||
if Instant::now().duration_since(start_time) > maximum_wait_time {
|
if Instant::now().duration_since(start_time) > maximum_wait_time {
|
||||||
anyhow::bail!("Timeout in starting geth");
|
anyhow::bail!(
|
||||||
|
"Timeout in starting geth: took longer than {}ms. stdout:\n\n{}\n",
|
||||||
|
self.start_timeout,
|
||||||
|
lines.join("\n")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,10 +260,13 @@ impl GethNode {
|
|||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
ProviderBuilder::new()
|
ProviderBuilder::new()
|
||||||
.disable_recommended_fillers()
|
.disable_recommended_fillers()
|
||||||
.filler(FallbackGasFiller::new(500_000_000, 500_000_000, 1))
|
.filler(FallbackGasFiller::new(
|
||||||
|
25_000_000,
|
||||||
|
1_000_000_000,
|
||||||
|
1_000_000_000,
|
||||||
|
))
|
||||||
.filler(ChainIdFiller::default())
|
.filler(ChainIdFiller::default())
|
||||||
.filler(NonceFiller::new(nonce_manager))
|
.filler(NonceFiller::new(nonce_manager))
|
||||||
.layer(CacheLayer::new(10_000))
|
|
||||||
.wallet(wallet)
|
.wallet(wallet)
|
||||||
.connect(&connection_string)
|
.connect(&connection_string)
|
||||||
.await
|
.await
|
||||||
@@ -268,14 +276,11 @@ impl GethNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EthereumNode for GethNode {
|
impl EthereumNode for GethNode {
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
async fn execute_transaction(
|
async fn execute_transaction(
|
||||||
&self,
|
&self,
|
||||||
transaction: TransactionRequest,
|
transaction: TransactionRequest,
|
||||||
) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> {
|
) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> {
|
||||||
let span = tracing::debug_span!("Submitting transaction", ?transaction);
|
|
||||||
let _guard = span.enter();
|
|
||||||
|
|
||||||
let provider = Arc::new(self.provider().await?);
|
let provider = Arc::new(self.provider().await?);
|
||||||
let transaction_hash = *provider.send_transaction(transaction).await?.tx_hash();
|
let transaction_hash = *provider.send_transaction(transaction).await?.tx_hash();
|
||||||
|
|
||||||
@@ -324,7 +329,7 @@ impl EthereumNode for GethNode {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
async fn trace_transaction(
|
async fn trace_transaction(
|
||||||
&self,
|
&self,
|
||||||
transaction: &TransactionReceipt,
|
transaction: &TransactionReceipt,
|
||||||
@@ -357,7 +362,7 @@ impl EthereumNode for GethNode {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
async fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result<DiffMode> {
|
async fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result<DiffMode> {
|
||||||
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
||||||
diff_mode: Some(true),
|
diff_mode: Some(true),
|
||||||
@@ -373,10 +378,33 @@ impl EthereumNode for GethNode {
|
|||||||
_ => anyhow::bail!("expected a diff mode trace"),
|
_ => anyhow::bail!("expected a diff mode trace"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
|
async fn balance_of(&self, address: Address) -> anyhow::Result<U256> {
|
||||||
|
self.provider()
|
||||||
|
.await?
|
||||||
|
.get_balance(address)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
|
async fn latest_state_proof(
|
||||||
|
&self,
|
||||||
|
address: Address,
|
||||||
|
keys: Vec<StorageKey>,
|
||||||
|
) -> anyhow::Result<EIP1186AccountProofResponse> {
|
||||||
|
self.provider()
|
||||||
|
.await?
|
||||||
|
.get_proof(address, keys)
|
||||||
|
.latest()
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResolverApi for GethNode {
|
impl ResolverApi for GethNode {
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
async fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
|
async fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -385,7 +413,7 @@ impl ResolverApi for GethNode {
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result<u128> {
|
async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result<u128> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -395,7 +423,7 @@ impl ResolverApi for GethNode {
|
|||||||
.map(|receipt| receipt.effective_gas_price)
|
.map(|receipt| receipt.effective_gas_price)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result<u128> {
|
async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result<u128> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -405,7 +433,7 @@ impl ResolverApi for GethNode {
|
|||||||
.map(|block| block.header.gas_limit as _)
|
.map(|block| block.header.gas_limit as _)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
async fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result<Address> {
|
async fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result<Address> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -415,7 +443,7 @@ impl ResolverApi for GethNode {
|
|||||||
.map(|block| block.header.beneficiary)
|
.map(|block| block.header.beneficiary)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result<U256> {
|
async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result<U256> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -425,7 +453,7 @@ impl ResolverApi for GethNode {
|
|||||||
.map(|block| U256::from_be_bytes(block.header.mix_hash.0))
|
.map(|block| U256::from_be_bytes(block.header.mix_hash.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result<u64> {
|
async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result<u64> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -440,7 +468,7 @@ impl ResolverApi for GethNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
async fn block_hash(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockHash> {
|
async fn block_hash(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockHash> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -450,7 +478,7 @@ impl ResolverApi for GethNode {
|
|||||||
.map(|block| block.header.hash)
|
.map(|block| block.header.hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
async fn block_timestamp(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockTimestamp> {
|
async fn block_timestamp(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockTimestamp> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -460,7 +488,7 @@ impl ResolverApi for GethNode {
|
|||||||
.map(|block| block.header.timestamp)
|
.map(|block| block.header.timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
async fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
async fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -493,7 +521,6 @@ impl Node for GethNode {
|
|||||||
geth: config.geth.clone(),
|
geth: config.geth.clone(),
|
||||||
id,
|
id,
|
||||||
handle: None,
|
handle: None,
|
||||||
network_id: config.network_id,
|
|
||||||
start_timeout: config.geth_start_timeout,
|
start_timeout: config.geth_start_timeout,
|
||||||
wallet,
|
wallet,
|
||||||
// We know that we only need to be storing 2 files so we can specify that when creating
|
// We know that we only need to be storing 2 files so we can specify that when creating
|
||||||
@@ -503,12 +530,12 @@ impl Node for GethNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn connection_string(&self) -> String {
|
fn connection_string(&self) -> String {
|
||||||
self.connection_string.clone()
|
self.connection_string.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
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.
|
// Terminate the processes in a graceful manner to allow for the output to be flushed.
|
||||||
if let Some(mut child) = self.handle.take() {
|
if let Some(mut child) = self.handle.take() {
|
||||||
@@ -530,13 +557,13 @@ impl Node for GethNode {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
fn spawn(&mut self, genesis: String) -> anyhow::Result<()> {
|
fn spawn(&mut self, genesis: String) -> anyhow::Result<()> {
|
||||||
self.init(genesis)?.spawn_process()?;
|
self.init(genesis)?.spawn_process()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||||
fn version(&self) -> anyhow::Result<String> {
|
fn version(&self) -> anyhow::Result<String> {
|
||||||
let output = Command::new(&self.geth)
|
let output = Command::new(&self.geth)
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
@@ -549,17 +576,21 @@ impl Node for GethNode {
|
|||||||
Ok(String::from_utf8_lossy(&output).into())
|
Ok(String::from_utf8_lossy(&output).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn matches_target(&self, targets: Option<&[String]>) -> bool {
|
fn matches_target(&self, targets: Option<&[String]>) -> bool {
|
||||||
match targets {
|
match targets {
|
||||||
None => true,
|
None => true,
|
||||||
Some(targets) => targets.iter().any(|str| str.as_str() == "evm"),
|
Some(targets) => targets.iter().any(|str| str.as_str() == "evm"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn evm_version() -> EVMVersion {
|
||||||
|
EVMVersion::Cancun
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for GethNode {
|
impl Drop for GethNode {
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.shutdown().expect("Failed to shutdown")
|
self.shutdown().expect("Failed to shutdown")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,22 +17,22 @@ use alloy::{
|
|||||||
},
|
},
|
||||||
primitives::{
|
primitives::{
|
||||||
Address, B64, B256, BlockHash, BlockNumber, BlockTimestamp, Bloom, Bytes, FixedBytes,
|
Address, B64, B256, BlockHash, BlockNumber, BlockTimestamp, Bloom, Bytes, FixedBytes,
|
||||||
TxHash, U256,
|
StorageKey, TxHash, U256,
|
||||||
},
|
},
|
||||||
providers::{
|
providers::{
|
||||||
Provider, ProviderBuilder,
|
Provider, ProviderBuilder,
|
||||||
ext::DebugApi,
|
ext::DebugApi,
|
||||||
fillers::{CachedNonceManager, ChainIdFiller, FillProvider, NonceFiller, TxFiller},
|
fillers::{CachedNonceManager, ChainIdFiller, FillProvider, NonceFiller, TxFiller},
|
||||||
layers::CacheLayer,
|
|
||||||
},
|
},
|
||||||
rpc::types::{
|
rpc::types::{
|
||||||
TransactionReceipt,
|
EIP1186AccountProofResponse, TransactionReceipt,
|
||||||
eth::{Block, Header, Transaction},
|
eth::{Block, Header, Transaction},
|
||||||
trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame},
|
trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame},
|
||||||
},
|
},
|
||||||
signers::local::PrivateKeySigner,
|
signers::local::PrivateKeySigner,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use revive_common::EVMVersion;
|
||||||
use revive_dt_common::fs::clear_directory;
|
use revive_dt_common::fs::clear_directory;
|
||||||
use revive_dt_format::traits::ResolverApi;
|
use revive_dt_format::traits::ResolverApi;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -160,7 +160,7 @@ impl KitchensinkNode {
|
|||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
fn spawn_process(&mut self) -> anyhow::Result<()> {
|
fn spawn_process(&mut self) -> anyhow::Result<()> {
|
||||||
let substrate_rpc_port = Self::BASE_SUBSTRATE_RPC_PORT + self.id as u16;
|
let substrate_rpc_port = Self::BASE_SUBSTRATE_RPC_PORT + self.id as u16;
|
||||||
let proxy_rpc_port = Self::BASE_PROXY_RPC_PORT + self.id as u16;
|
let proxy_rpc_port = Self::BASE_PROXY_RPC_PORT + self.id as u16;
|
||||||
@@ -258,7 +258,7 @@ impl KitchensinkNode {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
fn extract_balance_from_genesis_file(
|
fn extract_balance_from_genesis_file(
|
||||||
&self,
|
&self,
|
||||||
genesis: &Genesis,
|
genesis: &Genesis,
|
||||||
@@ -307,7 +307,7 @@ impl KitchensinkNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
pub fn eth_rpc_version(&self) -> anyhow::Result<String> {
|
pub fn eth_rpc_version(&self) -> anyhow::Result<String> {
|
||||||
let output = Command::new(&self.eth_proxy_binary)
|
let output = Command::new(&self.eth_proxy_binary)
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
@@ -367,13 +367,12 @@ impl KitchensinkNode {
|
|||||||
.disable_recommended_fillers()
|
.disable_recommended_fillers()
|
||||||
.network::<KitchenSinkNetwork>()
|
.network::<KitchenSinkNetwork>()
|
||||||
.filler(FallbackGasFiller::new(
|
.filler(FallbackGasFiller::new(
|
||||||
30_000_000,
|
25_000_000,
|
||||||
200_000_000_000,
|
1_000_000_000,
|
||||||
3_000_000_000,
|
1_000_000_000,
|
||||||
))
|
))
|
||||||
.filler(ChainIdFiller::default())
|
.filler(ChainIdFiller::default())
|
||||||
.filler(NonceFiller::new(nonce_manager))
|
.filler(NonceFiller::new(nonce_manager))
|
||||||
.layer(CacheLayer::new(10_000))
|
|
||||||
.wallet(wallet)
|
.wallet(wallet)
|
||||||
.connect(&connection_string)
|
.connect(&connection_string)
|
||||||
.await
|
.await
|
||||||
@@ -383,7 +382,7 @@ impl KitchensinkNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EthereumNode for KitchensinkNode {
|
impl EthereumNode for KitchensinkNode {
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
async fn execute_transaction(
|
async fn execute_transaction(
|
||||||
&self,
|
&self,
|
||||||
transaction: alloy::rpc::types::TransactionRequest,
|
transaction: alloy::rpc::types::TransactionRequest,
|
||||||
@@ -400,7 +399,7 @@ impl EthereumNode for KitchensinkNode {
|
|||||||
Ok(receipt)
|
Ok(receipt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
async fn trace_transaction(
|
async fn trace_transaction(
|
||||||
&self,
|
&self,
|
||||||
transaction: &TransactionReceipt,
|
transaction: &TransactionReceipt,
|
||||||
@@ -414,7 +413,7 @@ impl EthereumNode for KitchensinkNode {
|
|||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
async fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result<DiffMode> {
|
async fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result<DiffMode> {
|
||||||
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
||||||
diff_mode: Some(true),
|
diff_mode: Some(true),
|
||||||
@@ -430,10 +429,33 @@ impl EthereumNode for KitchensinkNode {
|
|||||||
_ => anyhow::bail!("expected a diff mode trace"),
|
_ => anyhow::bail!("expected a diff mode trace"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
|
async fn balance_of(&self, address: Address) -> anyhow::Result<U256> {
|
||||||
|
self.provider()
|
||||||
|
.await?
|
||||||
|
.get_balance(address)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
|
async fn latest_state_proof(
|
||||||
|
&self,
|
||||||
|
address: Address,
|
||||||
|
keys: Vec<StorageKey>,
|
||||||
|
) -> anyhow::Result<EIP1186AccountProofResponse> {
|
||||||
|
self.provider()
|
||||||
|
.await?
|
||||||
|
.get_proof(address, keys)
|
||||||
|
.latest()
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResolverApi for KitchensinkNode {
|
impl ResolverApi for KitchensinkNode {
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
async fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
|
async fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -442,7 +464,7 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result<u128> {
|
async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result<u128> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -452,7 +474,7 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map(|receipt| receipt.effective_gas_price)
|
.map(|receipt| receipt.effective_gas_price)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result<u128> {
|
async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result<u128> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -462,7 +484,7 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map(|block| block.header.gas_limit as _)
|
.map(|block| block.header.gas_limit as _)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
async fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result<Address> {
|
async fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result<Address> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -472,7 +494,7 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map(|block| block.header.beneficiary)
|
.map(|block| block.header.beneficiary)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result<U256> {
|
async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result<U256> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -482,7 +504,7 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map(|block| U256::from_be_bytes(block.header.mix_hash.0))
|
.map(|block| U256::from_be_bytes(block.header.mix_hash.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result<u64> {
|
async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result<u64> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -497,7 +519,7 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
async fn block_hash(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockHash> {
|
async fn block_hash(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockHash> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -507,7 +529,7 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map(|block| block.header.hash)
|
.map(|block| block.header.hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
async fn block_timestamp(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockTimestamp> {
|
async fn block_timestamp(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockTimestamp> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -517,7 +539,7 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map(|block| block.header.timestamp)
|
.map(|block| block.header.timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
async fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
async fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -565,7 +587,7 @@ impl Node for KitchensinkNode {
|
|||||||
self.rpc_url.clone()
|
self.rpc_url.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
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.
|
// Terminate the processes in a graceful manner to allow for the output to be flushed.
|
||||||
if let Some(mut child) = self.process_proxy.take() {
|
if let Some(mut child) = self.process_proxy.take() {
|
||||||
@@ -592,12 +614,12 @@ impl Node for KitchensinkNode {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
fn spawn(&mut self, genesis: String) -> anyhow::Result<()> {
|
fn spawn(&mut self, genesis: String) -> anyhow::Result<()> {
|
||||||
self.init(&genesis)?.spawn_process()
|
self.init(&genesis)?.spawn_process()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
||||||
fn version(&self) -> anyhow::Result<String> {
|
fn version(&self) -> anyhow::Result<String> {
|
||||||
let output = Command::new(&self.substrate_binary)
|
let output = Command::new(&self.substrate_binary)
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
@@ -617,6 +639,10 @@ impl Node for KitchensinkNode {
|
|||||||
Some(targets) => targets.iter().any(|str| str.as_str() == "pvm"),
|
Some(targets) => targets.iter().any(|str| str.as_str() == "pvm"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn evm_version() -> EVMVersion {
|
||||||
|
EVMVersion::Cancun
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for KitchensinkNode {
|
impl Drop for KitchensinkNode {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
//! This crate implements the testing nodes.
|
//! This crate implements the testing nodes.
|
||||||
|
|
||||||
|
use revive_common::EVMVersion;
|
||||||
use revive_dt_config::Arguments;
|
use revive_dt_config::Arguments;
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
|
|
||||||
@@ -36,4 +37,7 @@ pub trait Node: EthereumNode {
|
|||||||
/// Given a list of targets from the metadata file, this function determines if the metadata
|
/// Given a list of targets from the metadata file, this function determines if the metadata
|
||||||
/// file can be ran on this node or not.
|
/// file can be ran on this node or not.
|
||||||
fn matches_target(&self, targets: Option<&[String]>) -> bool;
|
fn matches_target(&self, targets: Option<&[String]>) -> bool;
|
||||||
|
|
||||||
|
/// Returns the EVM version of the node.
|
||||||
|
fn evm_version() -> EVMVersion;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
//! This crate implements concurrent handling of testing node.
|
//! This crate implements concurrent handling of testing node.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fs::read_to_string,
|
|
||||||
sync::atomic::{AtomicUsize, Ordering},
|
sync::atomic::{AtomicUsize, Ordering},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use revive_dt_common::cached_fs::read_to_string;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use revive_dt_config::Arguments;
|
use revive_dt_config::Arguments;
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ repository.workspace = true
|
|||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
revive-dt-common = { workspace = true }
|
||||||
revive-dt-config = { workspace = true }
|
revive-dt-config = { workspace = true }
|
||||||
revive-dt-format = { workspace = true }
|
revive-dt-format = { workspace = true }
|
||||||
revive-dt-compiler = { workspace = true }
|
revive-dt-compiler = { workspace = true }
|
||||||
|
|||||||
@@ -12,18 +12,19 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use revive_dt_compiler::{CompilerInput, CompilerOutput};
|
use serde::Serialize;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
|
use revive_dt_common::types::Mode;
|
||||||
|
use revive_dt_compiler::{CompilerInput, CompilerOutput};
|
||||||
use revive_dt_config::{Arguments, TestingPlatform};
|
use revive_dt_config::{Arguments, TestingPlatform};
|
||||||
use revive_dt_format::{corpus::Corpus, mode::SolcMode};
|
use revive_dt_format::corpus::Corpus;
|
||||||
|
|
||||||
use crate::analyzer::CompilerStatistics;
|
use crate::analyzer::CompilerStatistics;
|
||||||
|
|
||||||
pub(crate) static REPORTER: OnceLock<Mutex<Report>> = OnceLock::new();
|
pub(crate) static REPORTER: OnceLock<Mutex<Report>> = OnceLock::new();
|
||||||
|
|
||||||
/// The `Report` datastructure stores all relevant inforamtion required for generating reports.
|
/// The `Report` datastructure stores all relevant inforamtion required for generating reports.
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, Serialize)]
|
||||||
pub struct Report {
|
pub struct Report {
|
||||||
/// The configuration used during the test.
|
/// The configuration used during the test.
|
||||||
pub config: Arguments,
|
pub config: Arguments,
|
||||||
@@ -41,14 +42,14 @@ pub struct Report {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Contains a compiled contract.
|
/// Contains a compiled contract.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
pub struct CompilationTask {
|
pub struct CompilationTask {
|
||||||
/// The observed compiler input.
|
/// The observed compiler input.
|
||||||
pub json_input: CompilerInput,
|
pub json_input: CompilerInput,
|
||||||
/// The observed compiler output.
|
/// The observed compiler output.
|
||||||
pub json_output: Option<CompilerOutput>,
|
pub json_output: Option<CompilerOutput>,
|
||||||
/// The observed compiler mode.
|
/// The observed compiler mode.
|
||||||
pub mode: SolcMode,
|
pub mode: Mode,
|
||||||
/// The observed compiler version.
|
/// The observed compiler version.
|
||||||
pub compiler_version: String,
|
pub compiler_version: String,
|
||||||
/// The observed error, if any.
|
/// The observed error, if any.
|
||||||
@@ -56,7 +57,7 @@ pub struct CompilationTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a report about a compilation task.
|
/// Represents a report about a compilation task.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
pub struct CompilationResult {
|
pub struct CompilationResult {
|
||||||
/// The observed compilation task.
|
/// The observed compilation task.
|
||||||
pub compilation_task: CompilationTask,
|
pub compilation_task: CompilationTask,
|
||||||
@@ -65,7 +66,7 @@ pub struct CompilationResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The [Span] struct indicates the context of what is being reported.
|
/// The [Span] struct indicates the context of what is being reported.
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, Serialize)]
|
||||||
pub struct Span {
|
pub struct Span {
|
||||||
/// The corpus index this belongs to.
|
/// The corpus index this belongs to.
|
||||||
corpus: usize,
|
corpus: usize,
|
||||||
|
|||||||
Reference in New Issue
Block a user