mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-04-22 21:57:58 +00:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ad3d580df9 | |||
| d45b8da8e3 | |||
| 79ce4a239c | |||
| 609ececea6 | |||
| fb3959d345 | |||
| 84026f9aee | |||
| a7ce202a6b | |||
| c58551803d | |||
| 185edcfad9 | |||
| 09d56f5177 | |||
| e19e0a4e7a | |||
| a59e287fa1 | |||
| f2045db0e9 | |||
| 5a11f44673 | |||
| 46aea0890d | |||
| 9b40c9b9e3 | |||
| f67a9bf643 | |||
| 67d767ffde | |||
| f7fbe094ec | |||
| 90b2dd4cfe | |||
| 64d63ef999 | |||
| 757bfbe116 | |||
| 8619e7feb0 | |||
| edba49b301 | |||
| 9980926d40 | |||
| ff993d44a5 |
@@ -7,3 +7,5 @@ node_modules
|
|||||||
# We do not want to commit any log files that we produce from running the code locally so this is
|
# We do not want to commit any log files that we produce from running the code locally so this is
|
||||||
# added to the .gitignore file.
|
# added to the .gitignore file.
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
profile.json.gz
|
||||||
Generated
+610
-9
@@ -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,11 @@ 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",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4039,6 +4482,7 @@ dependencies = [
|
|||||||
"alloy",
|
"alloy",
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"dashmap",
|
||||||
"foundry-compilers-artifacts",
|
"foundry-compilers-artifacts",
|
||||||
"revive-common",
|
"revive-common",
|
||||||
"revive-dt-common",
|
"revive-dt-common",
|
||||||
@@ -4069,9 +4513,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",
|
||||||
@@ -4080,9 +4527,13 @@ 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-appender",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4094,6 +4545,9 @@ dependencies = [
|
|||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"alloy-sol-types",
|
"alloy-sol-types",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"futures",
|
||||||
|
"regex",
|
||||||
|
"revive-common",
|
||||||
"revive-dt-common",
|
"revive-dt-common",
|
||||||
"semver 1.0.26",
|
"semver 1.0.26",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -4108,6 +4562,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",
|
||||||
@@ -4134,12 +4589,12 @@ 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",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tracing",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4420,6 +4875,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"
|
||||||
@@ -4574,6 +5035,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",
|
||||||
@@ -4617,7 +5079,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",
|
||||||
@@ -4651,6 +5113,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"
|
||||||
@@ -5125,6 +5609,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"
|
||||||
@@ -5272,6 +5773,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"
|
||||||
@@ -5603,6 +6110,18 @@ dependencies = [
|
|||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-appender"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"time",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.28"
|
version = "0.1.28"
|
||||||
@@ -5769,6 +6288,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"
|
||||||
@@ -5804,12 +6329,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"
|
||||||
@@ -6065,6 +6608,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"
|
||||||
@@ -6078,6 +6643,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"
|
||||||
@@ -6102,9 +6678,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"
|
||||||
@@ -6194,6 +6780,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"
|
||||||
@@ -6323,6 +6918,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"
|
||||||
|
|||||||
+12
-2
@@ -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,15 @@ 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"] }
|
||||||
|
dashmap = { version = "6.1.0" }
|
||||||
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,19 +41,22 @@ 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",
|
||||||
"rt",
|
"rt",
|
||||||
] }
|
] }
|
||||||
uuid = { version = "1.8", features = ["v4"] }
|
uuid = { version = "1.8", features = ["v4"] }
|
||||||
tracing = "0.1.41"
|
tracing = { version = "0.1.41" }
|
||||||
|
tracing-appender = { version = "0.2.3" }
|
||||||
tracing-subscriber = { version = "0.3.19", default-features = false, features = [
|
tracing-subscriber = { version = "0.3.19", default-features = false, features = [
|
||||||
"fmt",
|
"fmt",
|
||||||
"json",
|
"json",
|
||||||
@@ -83,3 +91,5 @@ features = [
|
|||||||
inherits = "release"
|
inherits = "release"
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -10,4 +10,11 @@ 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"] }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mod poll;
|
||||||
|
|
||||||
|
pub use poll::*;
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
use std::ops::ControlFlow;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::{Result, anyhow};
|
||||||
|
|
||||||
|
const EXPONENTIAL_BACKOFF_MAX_WAIT_DURATION: Duration = Duration::from_secs(60);
|
||||||
|
|
||||||
|
/// A function that polls for a fallible future for some period of time and errors if it fails to
|
||||||
|
/// get a result after polling.
|
||||||
|
///
|
||||||
|
/// Given a future that returns a [`Result<ControlFlow<O, ()>>`], this function calls the future
|
||||||
|
/// repeatedly (with some wait period) until the future returns a [`ControlFlow::Break`] or until it
|
||||||
|
/// returns an [`Err`] in which case the function stops polling and returns the error.
|
||||||
|
///
|
||||||
|
/// If the future keeps returning [`ControlFlow::Continue`] and fails to return a [`Break`] within
|
||||||
|
/// the permitted polling duration then this function returns an [`Err`]
|
||||||
|
///
|
||||||
|
/// [`Break`]: ControlFlow::Break
|
||||||
|
/// [`Continue`]: ControlFlow::Continue
|
||||||
|
pub async fn poll<F, O>(
|
||||||
|
polling_duration: Duration,
|
||||||
|
polling_wait_behavior: PollingWaitBehavior,
|
||||||
|
mut future: impl FnMut() -> F,
|
||||||
|
) -> Result<O>
|
||||||
|
where
|
||||||
|
F: Future<Output = Result<ControlFlow<O, ()>>>,
|
||||||
|
{
|
||||||
|
let mut retries = 0;
|
||||||
|
let mut total_wait_duration = Duration::ZERO;
|
||||||
|
let max_allowed_wait_duration = polling_duration;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if total_wait_duration >= max_allowed_wait_duration {
|
||||||
|
break Err(anyhow!(
|
||||||
|
"Polling failed after {} retries and a total of {:?} of wait time",
|
||||||
|
retries,
|
||||||
|
total_wait_duration
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
match future().await? {
|
||||||
|
ControlFlow::Continue(()) => {
|
||||||
|
let next_wait_duration = match polling_wait_behavior {
|
||||||
|
PollingWaitBehavior::Constant(duration) => duration,
|
||||||
|
PollingWaitBehavior::ExponentialBackoff => {
|
||||||
|
Duration::from_secs(2u64.pow(retries))
|
||||||
|
.min(EXPONENTIAL_BACKOFF_MAX_WAIT_DURATION)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let next_wait_duration =
|
||||||
|
next_wait_duration.min(max_allowed_wait_duration - total_wait_duration);
|
||||||
|
total_wait_duration += next_wait_duration;
|
||||||
|
retries += 1;
|
||||||
|
|
||||||
|
tokio::time::sleep(next_wait_duration).await;
|
||||||
|
}
|
||||||
|
ControlFlow::Break(output) => {
|
||||||
|
break Ok(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
|
pub enum PollingWaitBehavior {
|
||||||
|
Constant(Duration),
|
||||||
|
#[default]
|
||||||
|
ExponentialBackoff,
|
||||||
|
}
|
||||||
@@ -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,7 +1,9 @@
|
|||||||
//! 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 iterators;
|
pub mod iterators;
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! impl_for_wrapper {
|
||||||
|
(Display, $ident: ident) => {
|
||||||
|
impl std::fmt::Display for $ident {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
std::fmt::Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Defines wrappers around types.
|
/// Defines wrappers around types.
|
||||||
///
|
///
|
||||||
/// For example, the macro invocation seen below:
|
/// For example, the macro invocation seen below:
|
||||||
@@ -42,7 +53,13 @@
|
|||||||
macro_rules! define_wrapper_type {
|
macro_rules! define_wrapper_type {
|
||||||
(
|
(
|
||||||
$(#[$meta: meta])*
|
$(#[$meta: meta])*
|
||||||
$vis:vis struct $ident: ident($ty: ty);
|
$vis:vis struct $ident: ident($ty: ty)
|
||||||
|
|
||||||
|
$(
|
||||||
|
impl $($trait_ident: ident),*
|
||||||
|
)?
|
||||||
|
|
||||||
|
;
|
||||||
) => {
|
) => {
|
||||||
$(#[$meta])*
|
$(#[$meta])*
|
||||||
$vis struct $ident($ty);
|
$vis struct $ident($ty);
|
||||||
@@ -98,9 +115,15 @@ macro_rules! define_wrapper_type {
|
|||||||
value.0
|
value.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(
|
||||||
|
$(
|
||||||
|
$crate::macros::impl_for_wrapper!($trait_ident, $ident);
|
||||||
|
)*
|
||||||
|
)?
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Technically not needed but this allows for the macro to be found in the `macros` module of the
|
/// Technically not needed but this allows for the macro to be found in the `macros` module of the
|
||||||
/// crate in addition to being found in the root of the crate.
|
/// crate in addition to being found in the root of the crate.
|
||||||
pub use define_wrapper_type;
|
pub use {define_wrapper_type, impl_for_wrapper};
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,9 +18,13 @@ revive-common = { workspace = true }
|
|||||||
alloy = { workspace = true }
|
alloy = { workspace = true }
|
||||||
alloy-primitives = { workspace = true }
|
alloy-primitives = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
dashmap = { workspace = true }
|
||||||
foundry-compilers-artifacts = { workspace = true }
|
foundry-compilers-artifacts = { workspace = true }
|
||||||
semver = { workspace = true }
|
semver = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -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);
|
||||||
+52
-10
@@ -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;
|
||||||
@@ -42,19 +47,27 @@ pub trait SolidityCompiler {
|
|||||||
version: impl Into<VersionOrRequirement>,
|
version: impl Into<VersionOrRequirement>,
|
||||||
) -> impl Future<Output = anyhow::Result<PathBuf>>;
|
) -> impl Future<Output = anyhow::Result<PathBuf>>;
|
||||||
|
|
||||||
fn version(&self) -> anyhow::Result<Version>;
|
fn version(&self) -> impl Future<Output = 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,
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
use std::{
|
use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
|
sync::LazyLock,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use dashmap::DashMap;
|
||||||
use revive_dt_common::types::VersionOrRequirement;
|
use revive_dt_common::types::VersionOrRequirement;
|
||||||
use revive_dt_config::Arguments;
|
use revive_dt_config::Arguments;
|
||||||
use revive_solc_json_interface::{
|
use revive_solc_json_interface::{
|
||||||
@@ -14,7 +16,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 +42,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 +89,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,
|
||||||
@@ -208,16 +221,23 @@ impl SolidityCompiler for Resolc {
|
|||||||
Ok(PathBuf::from("resolc"))
|
Ok(PathBuf::from("resolc"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn version(&self) -> anyhow::Result<semver::Version> {
|
async fn version(&self) -> anyhow::Result<semver::Version> {
|
||||||
// Logic for parsing the resolc version from the following string:
|
/// This is a cache of the path of the compiler to the version number of the compiler. We
|
||||||
// Solidity frontend for the revive compiler version 0.3.0+commit.b238913.llvm-18.1.8
|
/// choose to cache the version in this way rather than through a field on the struct since
|
||||||
|
/// compiler objects are being created all the time from the path and the compiler object is
|
||||||
|
/// not reused over time.
|
||||||
|
static VERSION_CACHE: LazyLock<DashMap<PathBuf, Version>> = LazyLock::new(Default::default);
|
||||||
|
|
||||||
|
match VERSION_CACHE.entry(self.resolc_path.clone()) {
|
||||||
|
dashmap::Entry::Occupied(occupied_entry) => Ok(occupied_entry.get().clone()),
|
||||||
|
dashmap::Entry::Vacant(vacant_entry) => {
|
||||||
let output = Command::new(self.resolc_path.as_path())
|
let output = Command::new(self.resolc_path.as_path())
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.spawn()?
|
.spawn()?
|
||||||
.wait_with_output()?
|
.wait_with_output()?
|
||||||
.stdout;
|
.stdout;
|
||||||
|
|
||||||
let output = String::from_utf8_lossy(&output);
|
let output = String::from_utf8_lossy(&output);
|
||||||
let version_string = output
|
let version_string = output
|
||||||
.split("version ")
|
.split("version ")
|
||||||
@@ -227,7 +247,25 @@ impl SolidityCompiler for Resolc {
|
|||||||
.next()
|
.next()
|
||||||
.context("Version parsing failed")?;
|
.context("Version parsing failed")?;
|
||||||
|
|
||||||
Version::parse(version_string).map_err(Into::into)
|
let version = Version::parse(version_string)?;
|
||||||
|
|
||||||
|
vacant_entry.insert(version.clone());
|
||||||
|
|
||||||
|
Ok(version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +283,7 @@ mod test {
|
|||||||
let compiler = Resolc::new(path);
|
let compiler = Resolc::new(path);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let version = compiler.version();
|
let version = compiler.version().await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
let _ = version.expect("Failed to get version");
|
let _ = version.expect("Failed to get version");
|
||||||
|
|||||||
+79
-12
@@ -4,13 +4,16 @@
|
|||||||
use std::{
|
use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
|
sync::LazyLock,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use dashmap::DashMap;
|
||||||
use revive_dt_common::types::VersionOrRequirement;
|
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 +38,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().await? >= 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 +70,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 +102,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()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -187,14 +211,22 @@ impl SolidityCompiler for Solc {
|
|||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn version(&self) -> anyhow::Result<semver::Version> {
|
async fn version(&self) -> anyhow::Result<semver::Version> {
|
||||||
// The following is the parsing code for the version from the solc version strings which
|
/// This is a cache of the path of the compiler to the version number of the compiler. We
|
||||||
// look like the following:
|
/// choose to cache the version in this way rather than through a field on the struct since
|
||||||
|
/// compiler objects are being created all the time from the path and the compiler object is
|
||||||
|
/// not reused over time.
|
||||||
|
static VERSION_CACHE: LazyLock<DashMap<PathBuf, Version>> = LazyLock::new(Default::default);
|
||||||
|
|
||||||
|
match VERSION_CACHE.entry(self.solc_path.clone()) {
|
||||||
|
dashmap::Entry::Occupied(occupied_entry) => Ok(occupied_entry.get().clone()),
|
||||||
|
dashmap::Entry::Vacant(vacant_entry) => {
|
||||||
|
// The following is the parsing code for the version from the solc version strings
|
||||||
|
// which look like the following:
|
||||||
// ```
|
// ```
|
||||||
// solc, the solidity compiler commandline interface
|
// solc, the solidity compiler commandline interface
|
||||||
// Version: 0.8.30+commit.73712a01.Darwin.appleclang
|
// Version: 0.8.30+commit.73712a01.Darwin.appleclang
|
||||||
// ```
|
// ```
|
||||||
|
|
||||||
let child = Command::new(self.solc_path.as_path())
|
let child = Command::new(self.solc_path.as_path())
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
@@ -210,7 +242,25 @@ impl SolidityCompiler for Solc {
|
|||||||
.next()
|
.next()
|
||||||
.context("Version parsing failed")?;
|
.context("Version parsing failed")?;
|
||||||
|
|
||||||
Version::parse(version_string).map_err(Into::into)
|
let version = Version::parse(version_string)?;
|
||||||
|
|
||||||
|
vacant_entry.insert(version.clone());
|
||||||
|
|
||||||
|
Ok(version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,15 +272,13 @@ mod test {
|
|||||||
async fn compiler_version_can_be_obtained() {
|
async fn compiler_version_can_be_obtained() {
|
||||||
// Arrange
|
// Arrange
|
||||||
let args = Arguments::default();
|
let args = Arguments::default();
|
||||||
println!("Getting compiler path");
|
|
||||||
let path = Solc::get_compiler_executable(&args, Version::new(0, 7, 6))
|
let path = Solc::get_compiler_executable(&args, Version::new(0, 7, 6))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("Got compiler path");
|
|
||||||
let compiler = Solc::new(path);
|
let compiler = Solc::new(path);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let version = compiler.version();
|
let version = compiler.version().await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -238,4 +286,23 @@ mod test {
|
|||||||
Version::new(0, 7, 6)
|
Version::new(0, 7, 6)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn compiler_version_can_be_obtained1() {
|
||||||
|
// Arrange
|
||||||
|
let args = Arguments::default();
|
||||||
|
let path = Solc::get_compiler_executable(&args, Version::new(0, 4, 21))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let compiler = Solc::new(path);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let version = compiler.version().await;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(
|
||||||
|
version.expect("Failed to get version"),
|
||||||
|
Version::new(0, 4, 21)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ async fn contracts_can_be_compiled_with_solc() {
|
|||||||
let compiler_path = Solc::get_compiler_executable(&args, Version::new(0, 8, 30))
|
let compiler_path = Solc::get_compiler_executable(&args, Version::new(0, 8, 30))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("About to assert");
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let output = Compiler::<Solc>::new()
|
let output = Compiler::<Solc>::new()
|
||||||
|
|||||||
@@ -15,3 +15,5 @@ semver = { workspace = true }
|
|||||||
temp-dir = { workspace = true }
|
temp-dir = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -77,7 +73,7 @@ pub struct Arguments {
|
|||||||
/// This argument controls which private keys the nodes should have access to and be added to
|
/// This argument controls which private keys the nodes should have access to and be added to
|
||||||
/// its wallet signers. With a value of N, private keys (0, N] will be added to the signer set
|
/// its wallet signers. With a value of N, private keys (0, N] will be added to the signer set
|
||||||
/// of the node.
|
/// of the node.
|
||||||
#[arg(long = "private-keys-count", default_value_t = 15_000)]
|
#[arg(long = "private-keys-count", default_value_t = 100_000)]
|
||||||
pub private_keys_to_add: usize,
|
pub private_keys_to_add: usize,
|
||||||
|
|
||||||
/// The differential testing leader node implementation.
|
/// The differential testing leader node implementation.
|
||||||
@@ -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,21 @@ 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-appender = { 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 }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -0,0 +1,262 @@
|
|||||||
|
//! 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()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
+322
-177
@@ -4,31 +4,36 @@ 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::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,
|
||||||
rpc::types::{
|
rpc::types::{TransactionRequest, trace::geth::DiffMode},
|
||||||
TransactionRequest,
|
|
||||||
trace::geth::{AccountState, DiffMode},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use futures::TryStreamExt;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
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;
|
||||||
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, StepIdx,
|
||||||
use revive_dt_format::{input::Input, metadata::Metadata};
|
StorageEmptyAssertion,
|
||||||
use revive_dt_node::Node;
|
};
|
||||||
|
use revive_dt_format::metadata::{ContractIdent, ContractInstance, ContractPathAndIdent};
|
||||||
|
use revive_dt_format::{input::Step, metadata::Metadata};
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
|
use tokio::try_join;
|
||||||
|
use tracing::{Instrument, info, info_span, instrument};
|
||||||
|
|
||||||
use crate::Platform;
|
use crate::Platform;
|
||||||
|
|
||||||
@@ -37,7 +42,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.
|
||||||
@@ -56,7 +61,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,
|
||||||
@@ -67,15 +72,41 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn handle_step(
|
||||||
|
&mut self,
|
||||||
|
metadata: &Metadata,
|
||||||
|
step: &Step,
|
||||||
|
node: &T::Blockchain,
|
||||||
|
) -> anyhow::Result<StepOutput> {
|
||||||
|
match step {
|
||||||
|
Step::FunctionCall(input) => {
|
||||||
|
let (receipt, geth_trace, diff_mode) =
|
||||||
|
self.handle_input(metadata, input, node).await?;
|
||||||
|
Ok(StepOutput::FunctionCall(receipt, geth_trace, diff_mode))
|
||||||
|
}
|
||||||
|
Step::BalanceAssertion(balance_assertion) => {
|
||||||
|
self.handle_balance_assertion(metadata, balance_assertion, node)
|
||||||
|
.await?;
|
||||||
|
Ok(StepOutput::BalanceAssertion)
|
||||||
|
}
|
||||||
|
Step::StorageEmptyAssertion(storage_empty) => {
|
||||||
|
self.handle_storage_empty(metadata, storage_empty, node)
|
||||||
|
.await?;
|
||||||
|
Ok(StepOutput::StorageEmptyAssertion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.inspect(|_| info!("Step Succeeded"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", name = "Handling Input", skip_all)]
|
||||||
pub async fn handle_input(
|
pub async fn handle_input(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
case_idx: CaseIdx,
|
|
||||||
input: &Input,
|
input: &Input,
|
||||||
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, 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)
|
||||||
@@ -84,27 +115,49 @@ where
|
|||||||
.handle_input_call_frame_tracing(&execution_receipt, node)
|
.handle_input_call_frame_tracing(&execution_receipt, node)
|
||||||
.await?;
|
.await?;
|
||||||
self.handle_input_variable_assignment(input, &tracing_result)?;
|
self.handle_input_variable_assignment(input, &tracing_result)?;
|
||||||
self.handle_input_expectations(input, &execution_receipt, node, &tracing_result)
|
let (_, (geth_trace, diff_mode)) = try_join!(
|
||||||
|
self.handle_input_expectations(input, &execution_receipt, node, &tracing_result),
|
||||||
|
self.handle_input_diff(&execution_receipt, node)
|
||||||
|
)?;
|
||||||
|
Ok((execution_receipt, geth_trace, diff_mode))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", name = "Handling Balance Assertion", skip_all)]
|
||||||
|
pub async fn handle_balance_assertion(
|
||||||
|
&mut self,
|
||||||
|
metadata: &Metadata,
|
||||||
|
balance_assertion: &BalanceAssertion,
|
||||||
|
node: &T::Blockchain,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
self.handle_balance_assertion_contract_deployment(metadata, balance_assertion, node)
|
||||||
.await?;
|
.await?;
|
||||||
self.handle_input_diff(case_idx, execution_receipt, node)
|
self.handle_balance_assertion_execution(balance_assertion, node)
|
||||||
.await
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", name = "Handling Storage Assertion", skip_all)]
|
||||||
|
pub async fn handle_storage_empty(
|
||||||
|
&mut self,
|
||||||
|
metadata: &Metadata,
|
||||||
|
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.
|
/// Handles the contract deployment for a given input performing it if it needs to be performed.
|
||||||
async fn handle_contract_deployment(
|
#[instrument(level = "info", skip_all)]
|
||||||
|
async fn handle_input_contract_deployment(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
case_idx: 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) {
|
||||||
@@ -116,11 +169,6 @@ where
|
|||||||
instances_we_must_deploy.insert(input.instance.clone(), true);
|
instances_we_must_deploy.insert(input.instance.clone(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!(
|
|
||||||
instances_to_deploy = instances_we_must_deploy.len(),
|
|
||||||
"Computed the number of required deployments for input"
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut receipts = HashMap::new();
|
let mut receipts = HashMap::new();
|
||||||
for (instance, deploy_with_constructor_arguments) in instances_we_must_deploy.into_iter() {
|
for (instance, deploy_with_constructor_arguments) in instances_we_must_deploy.into_iter() {
|
||||||
let calldata = deploy_with_constructor_arguments.then_some(&input.calldata);
|
let calldata = deploy_with_constructor_arguments.then_some(&input.calldata);
|
||||||
@@ -147,6 +195,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handles the execution of the input in terms of the calls that need to be made.
|
/// Handles the execution of the input in terms of the calls that need to be made.
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_execution(
|
async fn handle_input_execution(
|
||||||
&mut self,
|
&mut self,
|
||||||
input: &Input,
|
input: &Input,
|
||||||
@@ -161,36 +210,24 @@ where
|
|||||||
.context("Failed to find deployment receipt"),
|
.context("Failed to find deployment receipt"),
|
||||||
Method::Fallback | Method::FunctionName(_) => {
|
Method::Fallback | Method::FunctionName(_) => {
|
||||||
let tx = match input
|
let tx = match input
|
||||||
.legacy_transaction(&self.deployed_contracts, &self.variables, node)
|
.legacy_transaction(node, self.default_resolution_context())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(tx) => {
|
Ok(tx) => tx,
|
||||||
tracing::debug!("Legacy transaction data: {tx:#?}");
|
|
||||||
tx
|
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::error!("Failed to construct legacy transaction: {err:?}");
|
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::trace!("Executing transaction for input: {input:?}");
|
|
||||||
|
|
||||||
match node.execute_transaction(tx).await {
|
match node.execute_transaction(tx).await {
|
||||||
Ok(receipt) => Ok(receipt),
|
Ok(receipt) => Ok(receipt),
|
||||||
Err(err) => {
|
Err(err) => Err(err),
|
||||||
tracing::error!(
|
|
||||||
"Failed to execute transaction when executing the contract: {}, {:?}",
|
|
||||||
&*input.instance,
|
|
||||||
err
|
|
||||||
);
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_call_frame_tracing(
|
async fn handle_input_call_frame_tracing(
|
||||||
&self,
|
&self,
|
||||||
execution_receipt: &TransactionReceipt,
|
execution_receipt: &TransactionReceipt,
|
||||||
@@ -202,6 +239,14 @@ where
|
|||||||
tracer: Some(GethDebugTracerType::BuiltInTracer(
|
tracer: Some(GethDebugTracerType::BuiltInTracer(
|
||||||
GethDebugBuiltInTracerType::CallTracer,
|
GethDebugBuiltInTracerType::CallTracer,
|
||||||
)),
|
)),
|
||||||
|
tracer_config: GethDebugTracerConfig(serde_json::json! {{
|
||||||
|
"onlyTopCall": true,
|
||||||
|
"withLog": false,
|
||||||
|
"withStorage": false,
|
||||||
|
"withMemory": false,
|
||||||
|
"withStack": false,
|
||||||
|
"withReturnData": true
|
||||||
|
}}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -213,6 +258,7 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
fn handle_input_variable_assignment(
|
fn handle_input_variable_assignment(
|
||||||
&mut self,
|
&mut self,
|
||||||
input: &Input,
|
input: &Input,
|
||||||
@@ -233,21 +279,24 @@ where
|
|||||||
) {
|
) {
|
||||||
let value = U256::from_be_slice(output_word);
|
let value = U256::from_be_slice(output_word);
|
||||||
self.variables.insert(variable_name.clone(), value);
|
self.variables.insert(variable_name.clone(), value);
|
||||||
|
tracing::info!(
|
||||||
|
variable_name,
|
||||||
|
variable_value = hex::encode(value.to_be_bytes::<32>()),
|
||||||
|
"Assigned variable"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_expectations(
|
async fn handle_input_expectations(
|
||||||
&mut self,
|
&self,
|
||||||
input: &Input,
|
input: &Input,
|
||||||
execution_receipt: &TransactionReceipt,
|
execution_receipt: &TransactionReceipt,
|
||||||
node: &T::Blockchain,
|
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 {
|
||||||
@@ -276,24 +325,25 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for expectation in expectations.iter() {
|
futures::stream::iter(expectations.into_iter().map(Ok))
|
||||||
|
.try_for_each_concurrent(None, |expectation| async move {
|
||||||
self.handle_input_expectation_item(
|
self.handle_input_expectation_item(
|
||||||
execution_receipt,
|
execution_receipt,
|
||||||
node,
|
resolver,
|
||||||
expectation,
|
expectation,
|
||||||
tracing_result,
|
tracing_result,
|
||||||
)
|
)
|
||||||
.await?;
|
.await
|
||||||
}
|
})
|
||||||
|
.await
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_expectation_item(
|
async fn handle_input_expectation_item(
|
||||||
&mut self,
|
&self,
|
||||||
execution_receipt: &TransactionReceipt,
|
execution_receipt: &TransactionReceipt,
|
||||||
node: &T::Blockchain,
|
resolver: &impl ResolverApi,
|
||||||
expectation: &ExpectedOutput,
|
expectation: ExpectedOutput,
|
||||||
tracing_result: &CallFrame,
|
tracing_result: &CallFrame,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if let Some(ref version_requirement) = expectation.compiler_version {
|
if let Some(ref version_requirement) = expectation.compiler_version {
|
||||||
@@ -302,9 +352,10 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let deployed_contracts = &mut self.deployed_contracts;
|
let resolution_context = self
|
||||||
let variables = &mut self.variables;
|
.default_resolution_context()
|
||||||
let chain_state_provider = node;
|
.with_block_number(execution_receipt.block_number.as_ref())
|
||||||
|
.with_transaction_hash(&execution_receipt.transaction_hash);
|
||||||
|
|
||||||
// Handling the receipt state assertion.
|
// Handling the receipt state assertion.
|
||||||
let expected = !expectation.exception;
|
let expected = !expectation.exception;
|
||||||
@@ -327,12 +378,7 @@ where
|
|||||||
let expected = expected_calldata;
|
let expected = expected_calldata;
|
||||||
let actual = &tracing_result.output.as_ref().unwrap_or_default();
|
let actual = &tracing_result.output.as_ref().unwrap_or_default();
|
||||||
if !expected
|
if !expected
|
||||||
.is_equivalent(
|
.is_equivalent(actual, resolver, resolution_context)
|
||||||
actual,
|
|
||||||
deployed_contracts,
|
|
||||||
&*variables,
|
|
||||||
chain_state_provider,
|
|
||||||
)
|
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
@@ -358,14 +404,16 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handling the events assertion.
|
// Handling the events assertion.
|
||||||
for (expected_event, actual_event) in
|
for (event_idx, (expected_event, actual_event)) in expected_events
|
||||||
expected_events.iter().zip(execution_receipt.logs())
|
.iter()
|
||||||
|
.zip(execution_receipt.logs())
|
||||||
|
.enumerate()
|
||||||
{
|
{
|
||||||
// Handling the emitter assertion.
|
// Handling the emitter assertion.
|
||||||
if let Some(ref expected_address) = expected_event.address {
|
if let Some(ref expected_address) = expected_event.address {
|
||||||
let expected = Address::from_slice(
|
let expected = Address::from_slice(
|
||||||
Calldata::new_compound([expected_address])
|
Calldata::new_compound([expected_address])
|
||||||
.calldata(deployed_contracts, &*variables, node)
|
.calldata(resolver, resolution_context)
|
||||||
.await?
|
.await?
|
||||||
.get(12..32)
|
.get(12..32)
|
||||||
.expect("Can't fail"),
|
.expect("Can't fail"),
|
||||||
@@ -373,6 +421,7 @@ where
|
|||||||
let actual = actual_event.address();
|
let actual = actual_event.address();
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
|
event_idx,
|
||||||
%expected,
|
%expected,
|
||||||
%actual,
|
%actual,
|
||||||
"Event emitter assertion failed",
|
"Event emitter assertion failed",
|
||||||
@@ -392,15 +441,11 @@ where
|
|||||||
{
|
{
|
||||||
let expected = Calldata::new_compound([expected]);
|
let expected = Calldata::new_compound([expected]);
|
||||||
if !expected
|
if !expected
|
||||||
.is_equivalent(
|
.is_equivalent(&actual.0, resolver, resolution_context)
|
||||||
&actual.0,
|
|
||||||
deployed_contracts,
|
|
||||||
&*variables,
|
|
||||||
chain_state_provider,
|
|
||||||
)
|
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
|
event_idx,
|
||||||
?execution_receipt,
|
?execution_receipt,
|
||||||
?expected,
|
?expected,
|
||||||
?actual,
|
?actual,
|
||||||
@@ -416,15 +461,11 @@ where
|
|||||||
let expected = &expected_event.values;
|
let expected = &expected_event.values;
|
||||||
let actual = &actual_event.data().data;
|
let actual = &actual_event.data().data;
|
||||||
if !expected
|
if !expected
|
||||||
.is_equivalent(
|
.is_equivalent(&actual.0, resolver, resolution_context)
|
||||||
&actual.0,
|
|
||||||
deployed_contracts,
|
|
||||||
&*variables,
|
|
||||||
chain_state_provider,
|
|
||||||
)
|
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
|
event_idx,
|
||||||
?execution_receipt,
|
?execution_receipt,
|
||||||
?expected,
|
?expected,
|
||||||
?actual,
|
?actual,
|
||||||
@@ -440,15 +481,12 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_diff(
|
async fn handle_input_diff(
|
||||||
&mut self,
|
&self,
|
||||||
_: CaseIdx,
|
execution_receipt: &TransactionReceipt,
|
||||||
execution_receipt: TransactionReceipt,
|
|
||||||
node: &T::Blockchain,
|
node: &T::Blockchain,
|
||||||
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
) -> anyhow::Result<(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,
|
||||||
@@ -456,11 +494,137 @@ where
|
|||||||
});
|
});
|
||||||
|
|
||||||
let trace = node
|
let trace = node
|
||||||
.trace_transaction(&execution_receipt, trace_options)
|
.trace_transaction(execution_receipt, trace_options)
|
||||||
.await?;
|
.await?;
|
||||||
let diff = node.state_diff(&execution_receipt).await?;
|
let diff = node.state_diff(execution_receipt).await?;
|
||||||
|
|
||||||
Ok((execution_receipt, trace, diff))
|
Ok((trace, diff))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
|
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
|
||||||
@@ -478,7 +642,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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,7 +651,6 @@ where
|
|||||||
contract_ident,
|
contract_ident,
|
||||||
}) = metadata.contract_sources()?.remove(contract_instance)
|
}) = metadata.contract_sources()?.remove(contract_instance)
|
||||||
else {
|
else {
|
||||||
tracing::error!("Contract source not found for instance");
|
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"Contract source not found for instance {:?}",
|
"Contract source not found for instance {:?}",
|
||||||
contract_instance
|
contract_instance
|
||||||
@@ -500,11 +663,6 @@ where
|
|||||||
.and_then(|source_file_contracts| source_file_contracts.get(contract_ident.as_ref()))
|
.and_then(|source_file_contracts| source_file_contracts.get(contract_ident.as_ref()))
|
||||||
.cloned()
|
.cloned()
|
||||||
else {
|
else {
|
||||||
tracing::error!(
|
|
||||||
contract_source_path = contract_source_path.display().to_string(),
|
|
||||||
contract_ident = contract_ident.as_ref(),
|
|
||||||
"Failed to find information for contract"
|
|
||||||
);
|
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"Failed to find information for contract {:?}",
|
"Failed to find information for contract {:?}",
|
||||||
contract_instance
|
contract_instance
|
||||||
@@ -526,7 +684,7 @@ where
|
|||||||
|
|
||||||
if let Some(calldata) = calldata {
|
if let Some(calldata) = calldata {
|
||||||
let calldata = calldata
|
let calldata = calldata
|
||||||
.calldata(&self.deployed_contracts, None, node)
|
.calldata(node, self.default_resolution_context())
|
||||||
.await?;
|
.await?;
|
||||||
code.extend(calldata);
|
code.extend(calldata);
|
||||||
}
|
}
|
||||||
@@ -553,7 +711,6 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
let Some(address) = receipt.contract_address else {
|
let Some(address) = 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");
|
||||||
};
|
};
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
@@ -562,17 +719,24 @@ 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)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_resolution_context(&self) -> ResolutionContext<'_> {
|
||||||
|
ResolutionContext::default()
|
||||||
|
.with_deployed_contracts(&self.deployed_contracts)
|
||||||
|
.with_variables(&self.variables)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CaseDriver<'a, Leader: Platform, Follower: Platform> {
|
pub struct CaseDriver<'a, Leader: Platform, Follower: Platform> {
|
||||||
metadata: &'a Metadata,
|
metadata: &'a Metadata,
|
||||||
case: &'a Case,
|
case: &'a Case,
|
||||||
case_idx: CaseIdx,
|
|
||||||
leader_node: &'a Leader::Blockchain,
|
leader_node: &'a Leader::Blockchain,
|
||||||
follower_node: &'a Follower::Blockchain,
|
follower_node: &'a Follower::Blockchain,
|
||||||
leader_state: CaseState<Leader>,
|
leader_state: CaseState<Leader>,
|
||||||
@@ -588,7 +752,6 @@ where
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
metadata: &'a Metadata,
|
metadata: &'a Metadata,
|
||||||
case: &'a Case,
|
case: &'a Case,
|
||||||
case_idx: impl Into<CaseIdx>,
|
|
||||||
leader_node: &'a L::Blockchain,
|
leader_node: &'a L::Blockchain,
|
||||||
follower_node: &'a F::Blockchain,
|
follower_node: &'a F::Blockchain,
|
||||||
leader_state: CaseState<L>,
|
leader_state: CaseState<L>,
|
||||||
@@ -597,7 +760,6 @@ where
|
|||||||
Self {
|
Self {
|
||||||
metadata,
|
metadata,
|
||||||
case,
|
case,
|
||||||
case_idx: case_idx.into(),
|
|
||||||
leader_node,
|
leader_node,
|
||||||
follower_node,
|
follower_node,
|
||||||
leader_state,
|
leader_state,
|
||||||
@@ -605,78 +767,61 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trace_diff_mode(label: &str, diff: &DiffMode) {
|
#[instrument(level = "info", name = "Executing Case", skip_all)]
|
||||||
tracing::trace!("{label} - PRE STATE:");
|
|
||||||
for (addr, state) in &diff.pre {
|
|
||||||
Self::trace_account_state(" [pre]", addr, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::trace!("{label} - POST STATE:");
|
|
||||||
for (addr, state) in &diff.post {
|
|
||||||
Self::trace_account_state(" [post]", addr, state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn trace_account_state(prefix: &str, addr: &Address, state: &AccountState) {
|
|
||||||
tracing::trace!("{prefix} 0x{addr:x}");
|
|
||||||
|
|
||||||
if let Some(balance) = &state.balance {
|
|
||||||
tracing::trace!("{prefix} balance: {balance}");
|
|
||||||
}
|
|
||||||
if let Some(nonce) = &state.nonce {
|
|
||||||
tracing::trace!("{prefix} nonce: {nonce}");
|
|
||||||
}
|
|
||||||
if let Some(code) = &state.code {
|
|
||||||
tracing::trace!("{prefix} code: {code}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn execute(&mut self) -> anyhow::Result<usize> {
|
pub async fn execute(&mut self) -> anyhow::Result<usize> {
|
||||||
if !self
|
let mut steps_executed = 0;
|
||||||
.leader_node
|
for (step_idx, step) in self
|
||||||
.matches_target(self.metadata.targets.as_deref())
|
.case
|
||||||
|| !self
|
.steps_iterator()
|
||||||
.follower_node
|
.enumerate()
|
||||||
.matches_target(self.metadata.targets.as_deref())
|
.map(|(idx, v)| (StepIdx::new(idx), v))
|
||||||
{
|
{
|
||||||
tracing::warn!(
|
let (leader_step_output, follower_step_output) = try_join!(
|
||||||
targets = ?self.metadata.targets,
|
self.leader_state
|
||||||
"Either the leader or follower node do not support the targets of the file"
|
.handle_step(self.metadata, &step, self.leader_node)
|
||||||
);
|
.instrument(info_span!(
|
||||||
return Ok(0);
|
"Handling Step",
|
||||||
|
%step_idx,
|
||||||
|
target = "Leader",
|
||||||
|
)),
|
||||||
|
self.follower_state
|
||||||
|
.handle_step(self.metadata, &step, self.follower_node)
|
||||||
|
.instrument(info_span!(
|
||||||
|
"Handling Step",
|
||||||
|
%step_idx,
|
||||||
|
target = "Follower",
|
||||||
|
))
|
||||||
|
)?;
|
||||||
|
|
||||||
|
match (leader_step_output, follower_step_output) {
|
||||||
|
(StepOutput::FunctionCall(..), StepOutput::FunctionCall(..)) => {
|
||||||
|
// TODO: We need to actually work out how/if we will compare the diff between
|
||||||
|
// the leader and the follower. The diffs are almost guaranteed to be different
|
||||||
|
// from leader and follower and therefore without an actual strategy for this
|
||||||
|
// we have something that's guaranteed to fail. Even a simple call to some
|
||||||
|
// contract will produce two non-equal diffs because on the leader the contract
|
||||||
|
// has address X and on the follower it has address Y. On the leader contract X
|
||||||
|
// contains address A in the state and on the follower it contains address B. So
|
||||||
|
// this isn't exactly a straightforward thing to do and I'm not even sure that
|
||||||
|
// it's possible to do. Once we have an actual strategy for doing the diffs we
|
||||||
|
// will implement it here. Until then, this remains empty.
|
||||||
|
}
|
||||||
|
(StepOutput::BalanceAssertion, StepOutput::BalanceAssertion) => {}
|
||||||
|
(StepOutput::StorageEmptyAssertion, StepOutput::StorageEmptyAssertion) => {}
|
||||||
|
_ => unreachable!("The two step outputs can not be of a different kind"),
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut inputs_executed = 0;
|
steps_executed += 1;
|
||||||
for (input_idx, input) in self.case.inputs_iterator().enumerate() {
|
|
||||||
let tracing_span = tracing::info_span!("Handling input", input_idx);
|
|
||||||
let _guard = tracing_span.enter();
|
|
||||||
|
|
||||||
let (leader_receipt, _, leader_diff) = self
|
|
||||||
.leader_state
|
|
||||||
.handle_input(self.metadata, self.case_idx, &input, self.leader_node)
|
|
||||||
.await?;
|
|
||||||
let (follower_receipt, _, follower_diff) = self
|
|
||||||
.follower_state
|
|
||||||
.handle_input(self.metadata, self.case_idx, &input, self.follower_node)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if leader_diff == follower_diff {
|
|
||||||
tracing::debug!("State diffs match between leader and follower.");
|
|
||||||
} else {
|
|
||||||
tracing::debug!("State diffs mismatch between leader and follower.");
|
|
||||||
Self::trace_diff_mode("Leader", &leader_diff);
|
|
||||||
Self::trace_diff_mode("Follower", &follower_diff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if leader_receipt.logs() != follower_receipt.logs() {
|
Ok(steps_executed)
|
||||||
tracing::debug!("Log/event mismatch between leader and follower.");
|
|
||||||
tracing::trace!("Leader logs: {:?}", leader_receipt.logs());
|
|
||||||
tracing::trace!("Follower logs: {:?}", follower_receipt.logs());
|
|
||||||
}
|
|
||||||
|
|
||||||
inputs_executed += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(inputs_executed)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
pub enum StepOutput {
|
||||||
|
FunctionCall(TransactionReceipt, GethTrace, DiffMode),
|
||||||
|
BalanceAssertion,
|
||||||
|
StorageEmptyAssertion,
|
||||||
|
}
|
||||||
|
|||||||
+495
-414
File diff suppressed because it is too large
Load Diff
@@ -11,10 +11,14 @@ 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 }
|
||||||
|
futures = { 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"] }
|
||||||
@@ -22,3 +26,6 @@ serde_json = { workspace = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
+41
-15
@@ -1,31 +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, types::Mode};
|
||||||
|
|
||||||
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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
@@ -35,16 +54,23 @@ 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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn solc_modes(&self) -> Vec<Mode> {
|
||||||
|
match &self.modes {
|
||||||
|
Some(modes) => ParsedMode::many_to_modes(modes.iter()).collect(),
|
||||||
|
None => Mode::all().collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
define_wrapper_type!(
|
define_wrapper_type!(
|
||||||
/// A wrapper type for the index of test cases found in metadata file.
|
/// A wrapper type for the index of test cases found in metadata file.
|
||||||
#[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 Display;
|
||||||
);
|
);
|
||||||
|
|||||||
+88
-58
@@ -3,25 +3,28 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use revive_dt_common::iterators::FilesWithExtensionIterator;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tracing::{debug, info};
|
||||||
|
|
||||||
use crate::metadata::MetadataFile;
|
use crate::metadata::{Metadata, MetadataFile};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
pub struct Corpus {
|
#[serde(untagged)]
|
||||||
pub name: String,
|
pub enum Corpus {
|
||||||
pub path: PathBuf,
|
SinglePath { name: String, path: PathBuf },
|
||||||
|
MultiplePaths { name: String, paths: Vec<PathBuf> },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Corpus {
|
impl Corpus {
|
||||||
/// Try to read and parse the corpus definition file at given `path`.
|
pub fn try_from_path(file_path: impl AsRef<Path>) -> anyhow::Result<Self> {
|
||||||
pub fn try_from_path(path: &Path) -> anyhow::Result<Self> {
|
let mut corpus = File::open(file_path.as_ref())
|
||||||
let file = File::open(path)?;
|
.map_err(anyhow::Error::from)
|
||||||
let mut corpus: Corpus = serde_json::from_reader(file)?;
|
.and_then(|file| serde_json::from_reader::<_, Corpus>(file).map_err(Into::into))?;
|
||||||
|
|
||||||
// Ensure that the path mentioned in the corpus is relative to the corpus file.
|
for path in corpus.paths_iter_mut() {
|
||||||
// Canonicalizing also helps make the path in any errors unambiguous.
|
*path = file_path
|
||||||
corpus.path = path
|
.as_ref()
|
||||||
.parent()
|
.parent()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
anyhow::anyhow!("Corpus path '{}' does not point to a file", path.display())
|
anyhow::anyhow!("Corpus path '{}' does not point to a file", path.display())
|
||||||
@@ -33,67 +36,94 @@ impl Corpus {
|
|||||||
path.display()
|
path.display()
|
||||||
)
|
)
|
||||||
})?
|
})?
|
||||||
.join(corpus.path);
|
.join(path.as_path())
|
||||||
|
}
|
||||||
|
|
||||||
Ok(corpus)
|
Ok(corpus)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scan the corpus base directory and return all tests found.
|
|
||||||
pub fn enumerate_tests(&self) -> Vec<MetadataFile> {
|
pub fn enumerate_tests(&self) -> Vec<MetadataFile> {
|
||||||
let mut tests = Vec::new();
|
let mut tests = self
|
||||||
collect_metadata(&self.path, &mut tests);
|
.paths_iter()
|
||||||
|
.flat_map(|root_path| {
|
||||||
|
if !root_path.is_dir() {
|
||||||
|
Box::new(std::iter::once(root_path.to_path_buf()))
|
||||||
|
as Box<dyn Iterator<Item = _>>
|
||||||
|
} else {
|
||||||
|
Box::new(
|
||||||
|
FilesWithExtensionIterator::new(root_path)
|
||||||
|
.with_use_cached_fs(true)
|
||||||
|
.with_allowed_extension("sol")
|
||||||
|
.with_allowed_extension("json"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.map(move |metadata_file_path| (root_path, metadata_file_path))
|
||||||
|
})
|
||||||
|
.filter_map(|(root_path, metadata_file_path)| {
|
||||||
|
Metadata::try_from_file(&metadata_file_path)
|
||||||
|
.or_else(|| {
|
||||||
|
debug!(
|
||||||
|
discovered_from = %root_path.display(),
|
||||||
|
metadata_file_path = %metadata_file_path.display(),
|
||||||
|
"Skipping file since it doesn't contain valid metadata"
|
||||||
|
);
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.map(|metadata| MetadataFile {
|
||||||
|
metadata_file_path,
|
||||||
|
corpus_file_path: root_path.to_path_buf(),
|
||||||
|
content: metadata,
|
||||||
|
})
|
||||||
|
.inspect(|metadata_file| {
|
||||||
|
debug!(
|
||||||
|
metadata_file_path = %metadata_file.relative_path().display(),
|
||||||
|
"Loaded metadata file"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
tests.sort_by(|a, b| a.metadata_file_path.cmp(&b.metadata_file_path));
|
||||||
|
tests.dedup_by(|a, b| a.metadata_file_path == b.metadata_file_path);
|
||||||
|
info!(
|
||||||
|
len = tests.len(),
|
||||||
|
corpus_name = self.name(),
|
||||||
|
"Found tests in Corpus"
|
||||||
|
);
|
||||||
tests
|
tests
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Recursively walks `path` and parses any JSON or Solidity file into a test
|
pub fn name(&self) -> &str {
|
||||||
/// definition [Metadata].
|
match self {
|
||||||
///
|
Corpus::SinglePath { name, .. } | Corpus::MultiplePaths { name, .. } => name.as_str(),
|
||||||
/// Found tests are inserted into `tests`.
|
|
||||||
///
|
|
||||||
/// `path` is expected to be a directory.
|
|
||||||
pub fn collect_metadata(path: &Path, tests: &mut Vec<MetadataFile>) {
|
|
||||||
if path.is_dir() {
|
|
||||||
let dir_entry = match std::fs::read_dir(path) {
|
|
||||||
Ok(dir_entry) => dir_entry,
|
|
||||||
Err(error) => {
|
|
||||||
tracing::error!("failed to read dir '{}': {error}", path.display());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
for entry in dir_entry {
|
|
||||||
let entry = match entry {
|
|
||||||
Ok(entry) => entry,
|
|
||||||
Err(error) => {
|
|
||||||
tracing::error!("error reading dir entry: {error}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let path = entry.path();
|
|
||||||
if path.is_dir() {
|
|
||||||
collect_metadata(&path, tests);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if path.is_file() {
|
pub fn paths_iter(&self) -> impl Iterator<Item = &Path> {
|
||||||
if let Some(metadata) = MetadataFile::try_from_file(&path) {
|
match self {
|
||||||
tests.push(metadata)
|
Corpus::SinglePath { path, .. } => {
|
||||||
|
Box::new(std::iter::once(path.as_path())) as Box<dyn Iterator<Item = _>>
|
||||||
|
}
|
||||||
|
Corpus::MultiplePaths { paths, .. } => {
|
||||||
|
Box::new(paths.iter().map(|path| path.as_path())) as Box<dyn Iterator<Item = _>>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
let Some(extension) = path.extension() else {
|
pub fn paths_iter_mut(&mut self) -> impl Iterator<Item = &mut PathBuf> {
|
||||||
tracing::error!("Failed to get file extension");
|
match self {
|
||||||
return;
|
Corpus::SinglePath { path, .. } => {
|
||||||
};
|
Box::new(std::iter::once(path)) as Box<dyn Iterator<Item = _>>
|
||||||
if extension.eq_ignore_ascii_case("sol") || extension.eq_ignore_ascii_case("json") {
|
|
||||||
if let Some(metadata) = MetadataFile::try_from_file(path) {
|
|
||||||
tests.push(metadata)
|
|
||||||
}
|
}
|
||||||
} else {
|
Corpus::MultiplePaths { paths, .. } => {
|
||||||
tracing::error!(?extension, "Unsupported file extension");
|
Box::new(paths.iter_mut()) as Box<dyn Iterator<Item = _>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path_count(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Corpus::SinglePath { .. } => 1,
|
||||||
|
Corpus::MultiplePaths { paths, .. } => paths.len(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+269
-214
@@ -2,39 +2,109 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use alloy::{
|
use alloy::{
|
||||||
eips::BlockNumberOrTag,
|
eips::BlockNumberOrTag,
|
||||||
hex::ToHexExt,
|
json_abi::Function,
|
||||||
json_abi::JsonAbi,
|
|
||||||
network::TransactionBuilder,
|
network::TransactionBuilder,
|
||||||
primitives::{Address, Bytes, U256},
|
primitives::{Address, Bytes, U256},
|
||||||
rpc::types::TransactionRequest,
|
rpc::types::TransactionRequest,
|
||||||
};
|
};
|
||||||
use alloy_primitives::{FixedBytes, utils::parse_units};
|
use alloy_primitives::{FixedBytes, utils::parse_units};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, stream};
|
||||||
use semver::VersionReq;
|
use semver::VersionReq;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use revive_dt_common::macros::define_wrapper_type;
|
use revive_dt_common::macros::define_wrapper_type;
|
||||||
|
use tracing::{Instrument, info_span, instrument};
|
||||||
|
|
||||||
use crate::metadata::ContractInstance;
|
|
||||||
use crate::traits::ResolverApi;
|
use crate::traits::ResolverApi;
|
||||||
|
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>),
|
||||||
|
}
|
||||||
|
|
||||||
|
define_wrapper_type!(
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct StepIdx(usize) impl Display;
|
||||||
|
);
|
||||||
|
|
||||||
|
#[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),
|
||||||
@@ -42,17 +112,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,
|
||||||
@@ -109,7 +183,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),
|
||||||
@@ -120,7 +194,7 @@ define_wrapper_type! {
|
|||||||
/// This represents an item in the [`Calldata::Compound`] variant.
|
/// This represents an item in the [`Calldata::Compound`] variant.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct CalldataItem(String);
|
pub struct CalldataItem(String) impl Display;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
@@ -143,7 +217,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.
|
||||||
///
|
///
|
||||||
@@ -165,10 +239,10 @@ pub enum Method {
|
|||||||
|
|
||||||
define_wrapper_type!(
|
define_wrapper_type!(
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct EtherValue(U256);
|
pub struct EtherValue(U256) impl Display;
|
||||||
);
|
);
|
||||||
|
|
||||||
#[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.
|
||||||
///
|
///
|
||||||
@@ -187,50 +261,36 @@ impl Input {
|
|||||||
ContractInstance::new("Test")
|
ContractInstance::new("Test")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn instance_to_address(
|
pub async fn encoded_input(
|
||||||
&self,
|
&self,
|
||||||
instance: &ContractInstance,
|
resolver: &impl ResolverApi,
|
||||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
context: ResolutionContext<'_>,
|
||||||
) -> anyhow::Result<Address> {
|
|
||||||
deployed_contracts
|
|
||||||
.get(instance)
|
|
||||||
.map(|(a, _)| *a)
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("instance {instance:?} not deployed"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn encoded_input<'a>(
|
|
||||||
&'a self,
|
|
||||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
|
||||||
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
|
|
||||||
chain_state_provider: &impl ResolverApi,
|
|
||||||
) -> anyhow::Result<Bytes> {
|
) -> anyhow::Result<Bytes> {
|
||||||
match self.method {
|
match self.method {
|
||||||
Method::Deployer | Method::Fallback => {
|
Method::Deployer | Method::Fallback => {
|
||||||
let calldata = self
|
let calldata = self.calldata.calldata(resolver, context).await?;
|
||||||
.calldata
|
|
||||||
.calldata(deployed_contracts, variables, chain_state_provider)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(calldata.into())
|
Ok(calldata.into())
|
||||||
}
|
}
|
||||||
Method::FunctionName(ref function_name) => {
|
Method::FunctionName(ref function_name) => {
|
||||||
let Some(abi) = deployed_contracts.get(&self.instance).map(|(_, a)| a) else {
|
let Some(abi) = context.deployed_contract_abi(&self.instance) else {
|
||||||
tracing::error!(
|
|
||||||
contract_name = self.instance.as_ref(),
|
|
||||||
available_abis = ?deployed_contracts.keys().collect::<Vec<_>>(),
|
|
||||||
"Attempted to lookup ABI of contract but it wasn't found"
|
|
||||||
);
|
|
||||||
anyhow::bail!("ABI for instance '{}' not found", self.instance.as_ref());
|
anyhow::bail!("ABI for instance '{}' not found", self.instance.as_ref());
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::trace!("ABI found for instance: {}", &self.instance.as_ref());
|
|
||||||
|
|
||||||
// We follow the same logic that's implemented in the matter-labs-tester where they resolve
|
// We follow the same logic that's implemented in the matter-labs-tester where they resolve
|
||||||
// the function name into a function selector and they assume that he function doesn't have
|
// the function name into a function selector and they assume that he function doesn't have
|
||||||
// any existing overloads.
|
// any existing overloads.
|
||||||
|
// 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!(
|
||||||
@@ -238,14 +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!(
|
|
||||||
"Starting encoding ABI's parameters for instance: {}",
|
|
||||||
self.instance.as_ref()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Allocating a vector that we will be using for the calldata. The vector size will be:
|
// Allocating a vector that we will be using for the calldata. The vector size will be:
|
||||||
// 4 bytes for the function selector.
|
// 4 bytes for the function selector.
|
||||||
@@ -254,14 +309,9 @@ 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(
|
.calldata_into_slice(&mut calldata, resolver, context)
|
||||||
&mut calldata,
|
|
||||||
deployed_contracts,
|
|
||||||
variables,
|
|
||||||
chain_state_provider,
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(calldata.into())
|
Ok(calldata.into())
|
||||||
@@ -270,15 +320,12 @@ impl Input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse this input into a legacy transaction.
|
/// Parse this input into a legacy transaction.
|
||||||
pub async fn legacy_transaction<'a>(
|
pub async fn legacy_transaction(
|
||||||
&'a self,
|
&self,
|
||||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
resolver: &impl ResolverApi,
|
||||||
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
|
context: ResolutionContext<'_>,
|
||||||
chain_state_provider: &impl ResolverApi,
|
|
||||||
) -> anyhow::Result<TransactionRequest> {
|
) -> anyhow::Result<TransactionRequest> {
|
||||||
let input_data = self
|
let input_data = self.encoded_input(resolver, context).await?;
|
||||||
.encoded_input(deployed_contracts, variables, chain_state_provider)
|
|
||||||
.await?;
|
|
||||||
let transaction_request = TransactionRequest::default().from(self.caller).value(
|
let transaction_request = TransactionRequest::default().from(self.caller).value(
|
||||||
self.value
|
self.value
|
||||||
.map(|value| value.into_inner())
|
.map(|value| value.into_inner())
|
||||||
@@ -287,7 +334,10 @@ impl Input {
|
|||||||
match self.method {
|
match self.method {
|
||||||
Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)),
|
Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)),
|
||||||
_ => Ok(transaction_request
|
_ => Ok(transaction_request
|
||||||
.to(self.instance_to_address(&self.instance, deployed_contracts)?)
|
.to(context
|
||||||
|
.deployed_contract_address(&self.instance)
|
||||||
|
.context("Failed to get the contract address")
|
||||||
|
.copied()?)
|
||||||
.input(input_data.into())),
|
.input(input_data.into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,49 +406,40 @@ impl Calldata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn calldata<'a>(
|
pub async fn calldata(
|
||||||
&'a self,
|
&self,
|
||||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
resolver: &impl ResolverApi,
|
||||||
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
|
context: ResolutionContext<'_>,
|
||||||
chain_state_provider: &impl ResolverApi,
|
|
||||||
) -> anyhow::Result<Vec<u8>> {
|
) -> anyhow::Result<Vec<u8>> {
|
||||||
let mut buffer = Vec::<u8>::with_capacity(self.size_requirement());
|
let mut buffer = Vec::<u8>::with_capacity(self.size_requirement());
|
||||||
self.calldata_into_slice(
|
self.calldata_into_slice(&mut buffer, resolver, context)
|
||||||
&mut buffer,
|
|
||||||
deployed_contracts,
|
|
||||||
variables,
|
|
||||||
chain_state_provider,
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
Ok(buffer)
|
Ok(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn calldata_into_slice<'a>(
|
pub async fn calldata_into_slice(
|
||||||
&'a self,
|
&self,
|
||||||
buffer: &mut Vec<u8>,
|
buffer: &mut Vec<u8>,
|
||||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
resolver: &impl ResolverApi,
|
||||||
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
|
context: ResolutionContext<'_>,
|
||||||
chain_state_provider: &impl ResolverApi,
|
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Calldata::Single(bytes) => {
|
Calldata::Single(bytes) => {
|
||||||
buffer.extend_from_slice(bytes);
|
buffer.extend_from_slice(bytes);
|
||||||
}
|
}
|
||||||
Calldata::Compound(items) => {
|
Calldata::Compound(items) => {
|
||||||
for (arg_idx, arg) in items.iter().enumerate() {
|
let resolved = stream::iter(items.iter().enumerate())
|
||||||
match arg
|
.map(|(arg_idx, arg)| async move {
|
||||||
.resolve(deployed_contracts, variables.clone(), chain_state_provider)
|
arg.resolve(resolver, context)
|
||||||
|
.instrument(info_span!("Resolving argument", %arg, arg_idx))
|
||||||
|
.map_ok(|value| value.to_be_bytes::<32>())
|
||||||
.await
|
.await
|
||||||
{
|
})
|
||||||
Ok(resolved) => {
|
.buffered(0xFF)
|
||||||
buffer.extend(resolved.to_be_bytes::<32>());
|
.try_collect::<Vec<_>>()
|
||||||
}
|
.await?;
|
||||||
Err(error) => {
|
|
||||||
tracing::error!(?arg, arg_idx, ?error, "Failed to resolve argument");
|
buffer.extend(resolved.into_iter().flatten());
|
||||||
return Err(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -412,23 +453,21 @@ impl Calldata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if this [`Calldata`] is equivalent to the passed calldata bytes.
|
/// Checks if this [`Calldata`] is equivalent to the passed calldata bytes.
|
||||||
pub async fn is_equivalent<'a>(
|
pub async fn is_equivalent(
|
||||||
&'a self,
|
&self,
|
||||||
other: &[u8],
|
other: &[u8],
|
||||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
resolver: &impl ResolverApi,
|
||||||
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
|
context: ResolutionContext<'_>,
|
||||||
chain_state_provider: &impl ResolverApi,
|
|
||||||
) -> anyhow::Result<bool> {
|
) -> anyhow::Result<bool> {
|
||||||
match self {
|
match self {
|
||||||
Calldata::Single(calldata) => Ok(calldata == other),
|
Calldata::Single(calldata) => Ok(calldata == other),
|
||||||
Calldata::Compound(items) => {
|
Calldata::Compound(items) => {
|
||||||
// Chunking the "other" calldata into 32 byte chunks since each
|
stream::iter(items.iter().zip(other.chunks(32)))
|
||||||
// one of the items in the compound calldata represents 32 bytes
|
.map(|(this, other)| async move {
|
||||||
for (this, other) in items.iter().zip(other.chunks(32)) {
|
|
||||||
// The matterlabs format supports wildcards and therefore we
|
// The matterlabs format supports wildcards and therefore we
|
||||||
// also need to support them.
|
// also need to support them.
|
||||||
if this.as_ref() == "*" {
|
if this.as_ref() == "*" {
|
||||||
continue;
|
return Ok::<_, anyhow::Error>(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
let other = if other.len() < 32 {
|
let other = if other.len() < 32 {
|
||||||
@@ -439,32 +478,31 @@ impl Calldata {
|
|||||||
std::borrow::Cow::Borrowed(other)
|
std::borrow::Cow::Borrowed(other)
|
||||||
};
|
};
|
||||||
|
|
||||||
let this = this
|
let this = this.resolve(resolver, context).await?;
|
||||||
.resolve(deployed_contracts, variables.clone(), chain_state_provider)
|
|
||||||
.await?;
|
|
||||||
let other = U256::from_be_slice(&other);
|
let other = U256::from_be_slice(&other);
|
||||||
if this != other {
|
Ok(this == other)
|
||||||
return Ok(false);
|
})
|
||||||
}
|
.buffered(0xFF)
|
||||||
}
|
.all(|v| async move { v.is_ok_and(|v| v) })
|
||||||
Ok(true)
|
.map(Ok)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CalldataItem {
|
impl CalldataItem {
|
||||||
async fn resolve<'a>(
|
#[instrument(level = "info", skip_all, err)]
|
||||||
&'a self,
|
async fn resolve(
|
||||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
&self,
|
||||||
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
|
resolver: &impl ResolverApi,
|
||||||
chain_state_provider: &impl ResolverApi,
|
context: ResolutionContext<'_>,
|
||||||
) -> anyhow::Result<U256> {
|
) -> anyhow::Result<U256> {
|
||||||
let mut stack = Vec::<CalldataToken<U256>>::new();
|
let mut stack = Vec::<CalldataToken<U256>>::new();
|
||||||
|
|
||||||
for token in self
|
for token in self
|
||||||
.calldata_tokens()
|
.calldata_tokens()
|
||||||
.map(|token| token.resolve(deployed_contracts, variables.clone(), chain_state_provider))
|
.map(|token| token.resolve(resolver, context))
|
||||||
{
|
{
|
||||||
let token = token.await?;
|
let token = token.await?;
|
||||||
let new_token = match token {
|
let new_token = match token {
|
||||||
@@ -505,21 +543,14 @@ impl CalldataItem {
|
|||||||
match stack.as_slice() {
|
match stack.as_slice() {
|
||||||
// Empty stack means that we got an empty compound calldata which we resolve to zero.
|
// Empty stack means that we got an empty compound calldata which we resolve to zero.
|
||||||
[] => Ok(U256::ZERO),
|
[] => Ok(U256::ZERO),
|
||||||
[CalldataToken::Item(item)] => {
|
[CalldataToken::Item(item)] => Ok(*item),
|
||||||
tracing::debug!(
|
|
||||||
original = self.0,
|
|
||||||
resolved = item.to_be_bytes::<32>().encode_hex(),
|
|
||||||
"Resolved a Calldata item"
|
|
||||||
);
|
|
||||||
Ok(*item)
|
|
||||||
}
|
|
||||||
_ => Err(anyhow::anyhow!(
|
_ => Err(anyhow::anyhow!(
|
||||||
"Invalid calldata arithmetic operation - Invalid stack"
|
"Invalid calldata arithmetic operation - Invalid stack"
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calldata_tokens<'a>(&'a self) -> impl Iterator<Item = CalldataToken<&'a str>> + 'a {
|
fn calldata_tokens(&self) -> impl Iterator<Item = CalldataToken<&str>> {
|
||||||
self.0.split(' ').map(|item| match item {
|
self.0.split(' ').map(|item| match item {
|
||||||
"+" => CalldataToken::Operation(Operation::Addition),
|
"+" => CalldataToken::Operation(Operation::Addition),
|
||||||
"-" => CalldataToken::Operation(Operation::Subtraction),
|
"-" => CalldataToken::Operation(Operation::Subtraction),
|
||||||
@@ -543,9 +574,11 @@ impl<T> CalldataToken<T> {
|
|||||||
const GAS_LIMIT_VARIABLE: &str = "$GAS_LIMIT";
|
const GAS_LIMIT_VARIABLE: &str = "$GAS_LIMIT";
|
||||||
const COINBASE_VARIABLE: &str = "$COINBASE";
|
const COINBASE_VARIABLE: &str = "$COINBASE";
|
||||||
const DIFFICULTY_VARIABLE: &str = "$DIFFICULTY";
|
const DIFFICULTY_VARIABLE: &str = "$DIFFICULTY";
|
||||||
|
const BLOCK_BASE_FEE_VARIABLE: &str = "$BASE_FEE";
|
||||||
const BLOCK_HASH_VARIABLE_PREFIX: &str = "$BLOCK_HASH";
|
const BLOCK_HASH_VARIABLE_PREFIX: &str = "$BLOCK_HASH";
|
||||||
const BLOCK_NUMBER_VARIABLE: &str = "$BLOCK_NUMBER";
|
const BLOCK_NUMBER_VARIABLE: &str = "$BLOCK_NUMBER";
|
||||||
const BLOCK_TIMESTAMP_VARIABLE: &str = "$BLOCK_TIMESTAMP";
|
const BLOCK_TIMESTAMP_VARIABLE: &str = "$BLOCK_TIMESTAMP";
|
||||||
|
const TRANSACTION_GAS_PRICE: &str = "$TRANSACTION_GAS_PRICE";
|
||||||
const VARIABLE_PREFIX: &str = "$VARIABLE:";
|
const VARIABLE_PREFIX: &str = "$VARIABLE:";
|
||||||
|
|
||||||
fn into_item(self) -> Option<T> {
|
fn into_item(self) -> Option<T> {
|
||||||
@@ -565,24 +598,21 @@ impl<T: AsRef<str>> CalldataToken<T> {
|
|||||||
/// This piece of code is taken from the matter-labs-tester repository which is licensed under
|
/// This piece of code is taken from the matter-labs-tester repository which is licensed under
|
||||||
/// MIT or Apache. The original source code can be found here:
|
/// MIT or Apache. The original source code can be found here:
|
||||||
/// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146
|
/// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146
|
||||||
async fn resolve<'a>(
|
async fn resolve(
|
||||||
self,
|
self,
|
||||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
resolver: &impl ResolverApi,
|
||||||
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
|
context: ResolutionContext<'_>,
|
||||||
chain_state_provider: &impl ResolverApi,
|
|
||||||
) -> anyhow::Result<CalldataToken<U256>> {
|
) -> anyhow::Result<CalldataToken<U256>> {
|
||||||
match self {
|
match self {
|
||||||
Self::Item(item) => {
|
Self::Item(item) => {
|
||||||
let item = item.as_ref();
|
let item = item.as_ref();
|
||||||
let value = if let Some(instance) = item.strip_suffix(Self::ADDRESS_VARIABLE_SUFFIX)
|
let value = if let Some(instance) = item.strip_suffix(Self::ADDRESS_VARIABLE_SUFFIX)
|
||||||
{
|
{
|
||||||
Ok(U256::from_be_slice(
|
context
|
||||||
deployed_contracts
|
.deployed_contract_address(&ContractInstance::new(instance))
|
||||||
.get(&ContractInstance::new(instance))
|
.ok_or_else(|| anyhow::anyhow!("Instance `{}` not found", instance))
|
||||||
.map(|(a, _)| *a)
|
.map(AsRef::as_ref)
|
||||||
.ok_or_else(|| anyhow::anyhow!("Instance `{}` not found", instance))?
|
.map(U256::from_be_slice)
|
||||||
.as_ref(),
|
|
||||||
))
|
|
||||||
} else if let Some(value) = item.strip_prefix(Self::NEGATIVE_VALUE_PREFIX) {
|
} else if let Some(value) = item.strip_prefix(Self::NEGATIVE_VALUE_PREFIX) {
|
||||||
let value = U256::from_str_radix(value, 10).map_err(|error| {
|
let value = U256::from_str_radix(value, 10).map_err(|error| {
|
||||||
anyhow::anyhow!("Invalid decimal literal after `-`: {}", error)
|
anyhow::anyhow!("Invalid decimal literal after `-`: {}", error)
|
||||||
@@ -595,27 +625,36 @@ impl<T: AsRef<str>> CalldataToken<T> {
|
|||||||
.ok_or_else(|| anyhow::anyhow!("`-0` is invalid literal"))?;
|
.ok_or_else(|| anyhow::anyhow!("`-0` is invalid literal"))?;
|
||||||
Ok(U256::MAX.checked_sub(value).expect("Always valid"))
|
Ok(U256::MAX.checked_sub(value).expect("Always valid"))
|
||||||
} else if let Some(value) = item.strip_prefix(Self::HEX_LITERAL_PREFIX) {
|
} else if let Some(value) = item.strip_prefix(Self::HEX_LITERAL_PREFIX) {
|
||||||
Ok(U256::from_str_radix(value, 16).map_err(|error| {
|
U256::from_str_radix(value, 16)
|
||||||
anyhow::anyhow!("Invalid hexadecimal literal: {}", error)
|
.map_err(|error| anyhow::anyhow!("Invalid hexadecimal literal: {}", error))
|
||||||
})?)
|
|
||||||
} else if item == Self::CHAIN_VARIABLE {
|
} else if item == Self::CHAIN_VARIABLE {
|
||||||
let chain_id = chain_state_provider.chain_id().await?;
|
resolver.chain_id().await.map(U256::from)
|
||||||
Ok(U256::from(chain_id))
|
} else if item == Self::TRANSACTION_GAS_PRICE {
|
||||||
|
context
|
||||||
|
.transaction_hash()
|
||||||
|
.context("No transaction hash provided to get the transaction gas price")
|
||||||
|
.map(|tx_hash| resolver.transaction_gas_price(tx_hash))?
|
||||||
|
.await
|
||||||
|
.map(U256::from)
|
||||||
} else if item == Self::GAS_LIMIT_VARIABLE {
|
} else if item == Self::GAS_LIMIT_VARIABLE {
|
||||||
let gas_limit = chain_state_provider
|
resolver
|
||||||
.block_gas_limit(BlockNumberOrTag::Latest)
|
.block_gas_limit(context.resolve_block_number(BlockNumberOrTag::Latest))
|
||||||
.await?;
|
.await
|
||||||
Ok(U256::from(gas_limit))
|
.map(U256::from)
|
||||||
} else if item == Self::COINBASE_VARIABLE {
|
} else if item == Self::COINBASE_VARIABLE {
|
||||||
let coinbase = chain_state_provider
|
resolver
|
||||||
.block_coinbase(BlockNumberOrTag::Latest)
|
.block_coinbase(context.resolve_block_number(BlockNumberOrTag::Latest))
|
||||||
.await?;
|
.await
|
||||||
Ok(U256::from_be_slice(coinbase.as_ref()))
|
.map(|address| U256::from_be_slice(address.as_ref()))
|
||||||
} else if item == Self::DIFFICULTY_VARIABLE {
|
} else if item == Self::DIFFICULTY_VARIABLE {
|
||||||
let block_difficulty = chain_state_provider
|
resolver
|
||||||
.block_difficulty(BlockNumberOrTag::Latest)
|
.block_difficulty(context.resolve_block_number(BlockNumberOrTag::Latest))
|
||||||
.await?;
|
.await
|
||||||
Ok(block_difficulty)
|
} else if item == Self::BLOCK_BASE_FEE_VARIABLE {
|
||||||
|
resolver
|
||||||
|
.block_base_fee(context.resolve_block_number(BlockNumberOrTag::Latest))
|
||||||
|
.await
|
||||||
|
.map(U256::from)
|
||||||
} else if item.starts_with(Self::BLOCK_HASH_VARIABLE_PREFIX) {
|
} else if item.starts_with(Self::BLOCK_HASH_VARIABLE_PREFIX) {
|
||||||
let offset: u64 = item
|
let offset: u64 = item
|
||||||
.split(':')
|
.split(':')
|
||||||
@@ -623,35 +662,34 @@ impl<T: AsRef<str>> CalldataToken<T> {
|
|||||||
.and_then(|value| value.parse().ok())
|
.and_then(|value| value.parse().ok())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let current_block_number = chain_state_provider.last_block_number().await?;
|
let current_block_number = match context.tip_block_number() {
|
||||||
let desired_block_number = current_block_number - offset;
|
Some(block_number) => *block_number,
|
||||||
|
None => resolver.last_block_number().await?,
|
||||||
|
};
|
||||||
|
let desired_block_number = current_block_number.saturating_sub(offset);
|
||||||
|
|
||||||
let block_hash = chain_state_provider
|
let block_hash = resolver.block_hash(desired_block_number.into()).await?;
|
||||||
.block_hash(desired_block_number.into())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(U256::from_be_bytes(block_hash.0))
|
Ok(U256::from_be_bytes(block_hash.0))
|
||||||
} else if item == Self::BLOCK_NUMBER_VARIABLE {
|
} else if item == Self::BLOCK_NUMBER_VARIABLE {
|
||||||
let current_block_number = chain_state_provider.last_block_number().await?;
|
let current_block_number = match context.tip_block_number() {
|
||||||
|
Some(block_number) => *block_number,
|
||||||
|
None => resolver.last_block_number().await?,
|
||||||
|
};
|
||||||
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 {
|
||||||
let timestamp = chain_state_provider
|
resolver
|
||||||
.block_timestamp(BlockNumberOrTag::Latest)
|
.block_timestamp(context.resolve_block_number(BlockNumberOrTag::Latest))
|
||||||
.await?;
|
.await
|
||||||
Ok(U256::from(timestamp))
|
.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) {
|
||||||
let Some(variables) = variables.into() else {
|
context
|
||||||
anyhow::bail!(
|
.variable(variable_name)
|
||||||
"Variable resolution required but no variables were passed in"
|
.context("Variable lookup failed")
|
||||||
);
|
.copied()
|
||||||
};
|
|
||||||
let Some(variable) = variables.get(variable_name) else {
|
|
||||||
anyhow::bail!("No variable found with the name {}", variable_name)
|
|
||||||
};
|
|
||||||
Ok(*variable)
|
|
||||||
} else {
|
} else {
|
||||||
Ok(U256::from_str_radix(item, 10)
|
U256::from_str_radix(item, 10)
|
||||||
.map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))?)
|
.map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))
|
||||||
};
|
};
|
||||||
value.map(CalldataToken::Item)
|
value.map(CalldataToken::Item)
|
||||||
}
|
}
|
||||||
@@ -689,51 +727,52 @@ 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::json_abi::JsonAbi;
|
use alloy_primitives::{BlockHash, BlockNumber, BlockTimestamp, ChainId, TxHash, address};
|
||||||
use alloy_primitives::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 {
|
||||||
async fn chain_id(&self) -> anyhow::Result<alloy_primitives::ChainId> {
|
async fn chain_id(&self) -> anyhow::Result<ChainId> {
|
||||||
Ok(0x123)
|
Ok(0x123)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn block_gas_limit(&self, _: alloy::eips::BlockNumberOrTag) -> anyhow::Result<u128> {
|
async fn block_gas_limit(&self, _: BlockNumberOrTag) -> anyhow::Result<u128> {
|
||||||
Ok(0x1234)
|
Ok(0x1234)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn block_coinbase(
|
async fn block_coinbase(&self, _: BlockNumberOrTag) -> anyhow::Result<Address> {
|
||||||
&self,
|
|
||||||
_: alloy::eips::BlockNumberOrTag,
|
|
||||||
) -> anyhow::Result<Address> {
|
|
||||||
Ok(Address::ZERO)
|
Ok(Address::ZERO)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn block_difficulty(&self, _: alloy::eips::BlockNumberOrTag) -> anyhow::Result<U256> {
|
async fn block_difficulty(&self, _: BlockNumberOrTag) -> anyhow::Result<U256> {
|
||||||
Ok(U256::from(0x12345u128))
|
Ok(U256::from(0x12345u128))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn block_hash(
|
async fn block_base_fee(&self, _: BlockNumberOrTag) -> anyhow::Result<u64> {
|
||||||
&self,
|
Ok(0x100)
|
||||||
_: alloy::eips::BlockNumberOrTag,
|
}
|
||||||
) -> anyhow::Result<alloy_primitives::BlockHash> {
|
|
||||||
|
async fn block_hash(&self, _: BlockNumberOrTag) -> anyhow::Result<BlockHash> {
|
||||||
Ok([0xEE; 32].into())
|
Ok([0xEE; 32].into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn block_timestamp(
|
async fn block_timestamp(&self, _: BlockNumberOrTag) -> anyhow::Result<BlockTimestamp> {
|
||||||
&self,
|
|
||||||
_: alloy::eips::BlockNumberOrTag,
|
|
||||||
) -> anyhow::Result<alloy_primitives::BlockTimestamp> {
|
|
||||||
Ok(0x123456)
|
Ok(0x123456)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn last_block_number(&self) -> anyhow::Result<alloy_primitives::BlockNumber> {
|
async fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
||||||
Ok(0x1234567)
|
Ok(0x1234567)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn transaction_gas_price(&self, _: &TxHash) -> anyhow::Result<u128> {
|
||||||
|
Ok(0x200)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -769,13 +808,12 @@ 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 encoded = input
|
let resolver = MockResolver;
|
||||||
.encoded_input(&contracts, None, &MockResolver)
|
let context = ResolutionContext::default().with_deployed_contracts(&contracts);
|
||||||
.await
|
let encoded = input.encoded_input(&resolver, context).await.unwrap();
|
||||||
.unwrap();
|
|
||||||
assert!(encoded.0.starts_with(&selector));
|
assert!(encoded.0.starts_with(&selector));
|
||||||
|
|
||||||
type T = (u64,);
|
type T = (u64,);
|
||||||
@@ -814,13 +852,12 @@ 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 encoded = input
|
let resolver = MockResolver;
|
||||||
.encoded_input(&contracts, None, &MockResolver)
|
let context = ResolutionContext::default().with_deployed_contracts(&contracts);
|
||||||
.await
|
let encoded = input.encoded_input(&resolver, context).await.unwrap();
|
||||||
.unwrap();
|
|
||||||
assert!(encoded.0.starts_with(&selector));
|
assert!(encoded.0.starts_with(&selector));
|
||||||
|
|
||||||
type T = (alloy_primitives::Address,);
|
type T = (alloy_primitives::Address,);
|
||||||
@@ -862,13 +899,12 @@ 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 encoded = input
|
let resolver = MockResolver;
|
||||||
.encoded_input(&contracts, None, &MockResolver)
|
let context = ResolutionContext::default().with_deployed_contracts(&contracts);
|
||||||
.await
|
let encoded = input.encoded_input(&resolver, context).await.unwrap();
|
||||||
.unwrap();
|
|
||||||
assert!(encoded.0.starts_with(&selector));
|
assert!(encoded.0.starts_with(&selector));
|
||||||
|
|
||||||
type T = (alloy_primitives::Address,);
|
type T = (alloy_primitives::Address,);
|
||||||
@@ -881,12 +917,11 @@ 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)>,
|
||||||
chain_state_provider: &impl ResolverApi,
|
resolver: &impl ResolverApi,
|
||||||
) -> anyhow::Result<U256> {
|
) -> anyhow::Result<U256> {
|
||||||
CalldataItem::new(input)
|
let context = ResolutionContext::default().with_deployed_contracts(deployed_contracts);
|
||||||
.resolve(deployed_contracts, None, chain_state_provider)
|
CalldataItem::new(input).resolve(resolver, context).await
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -964,6 +999,26 @@ mod tests {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolver_can_resolve_block_base_fee_variable() {
|
||||||
|
// Arrange
|
||||||
|
let input = "$BASE_FEE";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver).await;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let resolved = resolved.expect("Failed to resolve argument");
|
||||||
|
assert_eq!(
|
||||||
|
resolved,
|
||||||
|
MockResolver
|
||||||
|
.block_base_fee(Default::default())
|
||||||
|
.await
|
||||||
|
.map(U256::from)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn resolver_can_resolve_block_hash_variable() {
|
async fn resolver_can_resolve_block_hash_variable() {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|||||||
+225
-63
@@ -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,12 +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 tracing::error;
|
||||||
|
|
||||||
|
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";
|
||||||
@@ -22,16 +25,26 @@ pub const SOLIDITY_CASE_COMMENT_MARKER: &str = "//!";
|
|||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
|
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
|
||||||
pub struct MetadataFile {
|
pub struct MetadataFile {
|
||||||
pub path: PathBuf,
|
/// The path of the metadata file. This will either be a JSON or solidity file.
|
||||||
|
pub metadata_file_path: PathBuf,
|
||||||
|
|
||||||
|
/// This is the path contained within the corpus file. This could either be the path of some dir
|
||||||
|
/// or could be the actual metadata file path.
|
||||||
|
pub corpus_file_path: PathBuf,
|
||||||
|
|
||||||
|
/// The metadata contained within the file.
|
||||||
pub content: Metadata,
|
pub content: Metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetadataFile {
|
impl MetadataFile {
|
||||||
pub fn try_from_file(path: &Path) -> Option<Self> {
|
pub fn relative_path(&self) -> &Path {
|
||||||
Metadata::try_from_file(path).map(|metadata| Self {
|
if self.corpus_file_path.is_file() {
|
||||||
path: path.to_owned(),
|
&self.corpus_file_path
|
||||||
content: metadata,
|
} else {
|
||||||
})
|
self.metadata_file_path
|
||||||
|
.strip_prefix(&self.corpus_file_path)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,34 +56,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.
|
||||||
@@ -126,10 +156,7 @@ impl Metadata {
|
|||||||
pub fn try_from_file(path: &Path) -> Option<Self> {
|
pub fn try_from_file(path: &Path) -> Option<Self> {
|
||||||
assert!(path.is_file(), "not a file: {}", path.display());
|
assert!(path.is_file(), "not a file: {}", path.display());
|
||||||
|
|
||||||
let Some(file_extension) = path.extension() else {
|
let file_extension = path.extension()?;
|
||||||
tracing::debug!("skipping corpus file: {}", path.display());
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
if file_extension == METADATA_FILE_EXTENSION {
|
if file_extension == METADATA_FILE_EXTENSION {
|
||||||
return Self::try_from_json(path);
|
return Self::try_from_json(path);
|
||||||
@@ -139,18 +166,12 @@ impl Metadata {
|
|||||||
return Self::try_from_solidity(path);
|
return Self::try_from_solidity(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!("ignoring invalid corpus file: {}", path.display());
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_from_json(path: &Path) -> Option<Self> {
|
fn try_from_json(path: &Path) -> Option<Self> {
|
||||||
let file = File::open(path)
|
let file = File::open(path)
|
||||||
.inspect_err(|error| {
|
.inspect_err(|err| error!(path = %path.display(), %err, "Failed to open file"))
|
||||||
tracing::error!(
|
|
||||||
"opening JSON test metadata file '{}' error: {error}",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
match serde_json::from_reader::<_, Metadata>(file) {
|
match serde_json::from_reader::<_, Metadata>(file) {
|
||||||
@@ -158,11 +179,8 @@ impl Metadata {
|
|||||||
metadata.file_path = Some(path.to_path_buf());
|
metadata.file_path = Some(path.to_path_buf());
|
||||||
Some(metadata)
|
Some(metadata)
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(err) => {
|
||||||
tracing::error!(
|
error!(path = %path.display(), %err, "Deserialization of metadata failed");
|
||||||
"parsing JSON test metadata file '{}' error: {error}",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,12 +188,7 @@ impl Metadata {
|
|||||||
|
|
||||||
fn try_from_solidity(path: &Path) -> Option<Self> {
|
fn try_from_solidity(path: &Path) -> Option<Self> {
|
||||||
let spec = read_to_string(path)
|
let spec = read_to_string(path)
|
||||||
.inspect_err(|error| {
|
.inspect_err(|err| error!(path = %path.display(), %err, "Failed to read file content"))
|
||||||
tracing::error!(
|
|
||||||
"opening JSON test metadata file '{}' error: {error}",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.ok()?
|
.ok()?
|
||||||
.lines()
|
.lines()
|
||||||
.filter_map(|line| line.strip_prefix(SOLIDITY_CASE_COMMENT_MARKER))
|
.filter_map(|line| line.strip_prefix(SOLIDITY_CASE_COMMENT_MARKER))
|
||||||
@@ -203,11 +216,8 @@ impl Metadata {
|
|||||||
);
|
);
|
||||||
Some(metadata)
|
Some(metadata)
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(err) => {
|
||||||
tracing::error!(
|
error!(path = %path.display(), %err, "Failed to deserialize metadata");
|
||||||
"parsing Solidity test metadata file '{}' error: '{error}' from data: {spec}",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +241,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),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,7 +257,7 @@ define_wrapper_type!(
|
|||||||
Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
|
Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
|
||||||
)]
|
)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct ContractInstance(String);
|
pub struct ContractInstance(String) impl Display;
|
||||||
);
|
);
|
||||||
|
|
||||||
define_wrapper_type!(
|
define_wrapper_type!(
|
||||||
@@ -256,7 +268,7 @@ define_wrapper_type!(
|
|||||||
Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
|
Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
|
||||||
)]
|
)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct ContractIdent(String);
|
pub struct ContractIdent(String) impl Display;
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Represents an identifier used for contracts.
|
/// Represents an identifier used for contracts.
|
||||||
@@ -343,6 +355,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::*;
|
||||||
|
|||||||
+241
-85
@@ -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 {
|
/// Mode strings can take the following form (in pseudo-regex):
|
||||||
Solidity(SolcMode),
|
///
|
||||||
Unknown(String),
|
/// ```text
|
||||||
|
/// [YEILV][+-]? (M[0123sz])? <semver>?
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// We can parse valid mode strings into [`ParsedMode`] using [`ParsedMode::from_str`].
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
|
#[serde(try_from = "String", into = "String")]
|
||||||
|
pub struct ParsedMode {
|
||||||
|
pub pipeline: Option<ModePipeline>,
|
||||||
|
pub optimize_flag: Option<bool>,
|
||||||
|
pub optimize_setting: Option<ModeOptimizerSetting>,
|
||||||
|
pub version: Option<semver::VersionReq>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify Solidity specific compiler options.
|
impl FromStr for ParsedMode {
|
||||||
#[derive(Hash, Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
type Err = anyhow::Error;
|
||||||
pub struct SolcMode {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
pub solc_version: Option<semver::VersionReq>,
|
static REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||||
solc_optimize: Option<bool>,
|
Regex::new(r"(?x)
|
||||||
pub llvm_optimizer_settings: Vec<String>,
|
^
|
||||||
}
|
(?:(?P<pipeline>[YEILV])(?P<optimize_flag>[+-])?)? # Pipeline to use eg Y, E+, E-
|
||||||
|
\s*
|
||||||
|
(?P<optimize_setting>M[a-zA-Z0-9])? # Optimize setting eg M0, Ms, Mz
|
||||||
|
\s*
|
||||||
|
(?P<version>[>=<]*\d+(?:\.\d+)*)? # Optional semver version eg >=0.8.0, 0.7, <0.8
|
||||||
|
$
|
||||||
|
").unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
impl SolcMode {
|
let Some(caps) = REGEX.captures(s) else {
|
||||||
/// Try to parse a mode string into a solc mode.
|
anyhow::bail!("Cannot parse mode '{s}' from string");
|
||||||
/// 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),
|
|
||||||
/// optionally followed by `+` or `-` for the solc optimizer settings.
|
|
||||||
///
|
|
||||||
/// Options can be separated by a whitespace contain the following
|
|
||||||
/// - A solc `SemVer version requirement` string
|
|
||||||
/// - 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();
|
|
||||||
|
|
||||||
let mut parts = mode_string.trim().split(" ");
|
|
||||||
|
|
||||||
match parts.next()? {
|
|
||||||
"Y" => {}
|
|
||||||
"Y+" => result.solc_optimize = Some(true),
|
|
||||||
"Y-" => result.solc_optimize = Some(false),
|
|
||||||
_ => return None,
|
|
||||||
}
|
|
||||||
|
|
||||||
for part in parts {
|
|
||||||
if let Ok(solc_version) = semver::VersionReq::parse(part) {
|
|
||||||
result.solc_version = Some(solc_version);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(level) = part.strip_prefix("-O") {
|
|
||||||
result.llvm_optimizer_settings.push(level.to_string());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
panic!("the YUL mode string {mode_string} failed to parse, invalid part: {part}")
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
if let Some(optimize_setting) = self.optimize_setting {
|
||||||
|
if has_written {
|
||||||
|
f.write_str(" ")?;
|
||||||
|
}
|
||||||
|
optimize_setting.fmt(f)?;
|
||||||
|
has_written = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves the [`SolcMode`]'s solidity version requirement into a [`VersionOrRequirement`] if
|
if let Some(version) = &self.version {
|
||||||
/// 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(),
|
version.fmt(f)?;
|
||||||
None => default.into(),
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParsedMode> for String {
|
||||||
|
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
|
||||||
|
A: Iterator,
|
||||||
|
B: Iterator<Item = A::Item>,
|
||||||
|
{
|
||||||
|
type Item = A::Item;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match self {
|
||||||
|
EitherIter::A(iter) => iter.next(),
|
||||||
|
EitherIter::B(iter) => iter.next(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Mode {
|
#[cfg(test)]
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
mod tests {
|
||||||
where
|
use super::*;
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let mode_string = String::deserialize(deserializer)?;
|
|
||||||
|
|
||||||
if let Some(solc_mode) = SolcMode::parse_from_mode_string(&mode_string) {
|
#[test]
|
||||||
return Ok(Self::Solidity(solc_mode));
|
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)
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to parse mode string '{actual}'"));
|
||||||
|
assert_eq!(
|
||||||
|
expected,
|
||||||
|
parsed.to_string(),
|
||||||
|
"Mode string '{actual}' did not parse to '{expected}': got '{parsed}'"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self::Unknown(mode_string))
|
#[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)
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to parse mode string '{actual}'"));
|
||||||
|
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:?}'"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use alloy::eips::BlockNumberOrTag;
|
use alloy::eips::BlockNumberOrTag;
|
||||||
|
use alloy::json_abi::JsonAbi;
|
||||||
use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, U256};
|
use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, U256};
|
||||||
|
use alloy_primitives::TxHash;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
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.
|
||||||
pub trait ResolverApi {
|
pub trait ResolverApi {
|
||||||
/// Returns the ID of the chain that the node is on.
|
/// Returns the ID of the chain that the node is on.
|
||||||
fn chain_id(&self) -> impl Future<Output = Result<ChainId>>;
|
fn chain_id(&self) -> impl Future<Output = Result<ChainId>>;
|
||||||
|
|
||||||
|
/// Returns the gas price for the specified transaction.
|
||||||
|
fn transaction_gas_price(&self, tx_hash: &TxHash) -> impl Future<Output = Result<u128>>;
|
||||||
|
|
||||||
// TODO: This is currently a u128 due to Kitchensink needing more than 64 bits for its gas limit
|
// TODO: This is currently a u128 due to Kitchensink needing more than 64 bits for its gas limit
|
||||||
// when we implement the changes to the gas we need to adjust this to be a u64.
|
// when we implement the changes to the gas we need to adjust this to be a u64.
|
||||||
/// Returns the gas limit of the specified block.
|
/// Returns the gas limit of the specified block.
|
||||||
@@ -19,6 +28,9 @@ pub trait ResolverApi {
|
|||||||
/// Returns the difficulty of the specified block.
|
/// Returns the difficulty of the specified block.
|
||||||
fn block_difficulty(&self, number: BlockNumberOrTag) -> impl Future<Output = Result<U256>>;
|
fn block_difficulty(&self, number: BlockNumberOrTag) -> impl Future<Output = Result<U256>>;
|
||||||
|
|
||||||
|
/// Returns the base fee of the specified block.
|
||||||
|
fn block_base_fee(&self, number: BlockNumberOrTag) -> impl Future<Output = Result<u64>>;
|
||||||
|
|
||||||
/// Returns the hash of the specified block.
|
/// Returns the hash of the specified block.
|
||||||
fn block_hash(&self, number: BlockNumberOrTag) -> impl Future<Output = Result<BlockHash>>;
|
fn block_hash(&self, number: BlockNumberOrTag) -> impl Future<Output = Result<BlockHash>>;
|
||||||
|
|
||||||
@@ -31,3 +43,115 @@ pub trait ResolverApi {
|
|||||||
/// Returns the number of the last block.
|
/// Returns the number of the last block.
|
||||||
fn last_block_number(&self) -> impl Future<Output = Result<BlockNumber>>;
|
fn last_block_number(&self) -> impl Future<Output = Result<BlockNumber>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
/// Contextual information required by the code that's performing the resolution.
|
||||||
|
pub struct ResolutionContext<'a> {
|
||||||
|
/// When provided the contracts provided here will be used for resolutions.
|
||||||
|
deployed_contracts: Option<&'a HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
|
||||||
|
|
||||||
|
/// When provided the variables in here will be used for performing resolutions.
|
||||||
|
variables: Option<&'a HashMap<String, U256>>,
|
||||||
|
|
||||||
|
/// When provided this block number will be treated as the tip of the chain.
|
||||||
|
block_number: Option<&'a BlockNumber>,
|
||||||
|
|
||||||
|
/// When provided the resolver will use this transaction hash for all of its resolutions.
|
||||||
|
transaction_hash: Option<&'a TxHash>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ResolutionContext<'a> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_from_parts(
|
||||||
|
deployed_contracts: impl Into<
|
||||||
|
Option<&'a HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
|
||||||
|
>,
|
||||||
|
variables: impl Into<Option<&'a HashMap<String, U256>>>,
|
||||||
|
block_number: impl Into<Option<&'a BlockNumber>>,
|
||||||
|
transaction_hash: impl Into<Option<&'a TxHash>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
deployed_contracts: deployed_contracts.into(),
|
||||||
|
variables: variables.into(),
|
||||||
|
block_number: block_number.into(),
|
||||||
|
transaction_hash: transaction_hash.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_deployed_contracts(
|
||||||
|
mut self,
|
||||||
|
deployed_contracts: impl Into<
|
||||||
|
Option<&'a HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
|
||||||
|
>,
|
||||||
|
) -> Self {
|
||||||
|
self.deployed_contracts = deployed_contracts.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_variables(
|
||||||
|
mut self,
|
||||||
|
variables: impl Into<Option<&'a HashMap<String, U256>>>,
|
||||||
|
) -> Self {
|
||||||
|
self.variables = variables.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_block_number(mut self, block_number: impl Into<Option<&'a BlockNumber>>) -> Self {
|
||||||
|
self.block_number = block_number.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_transaction_hash(
|
||||||
|
mut self,
|
||||||
|
transaction_hash: impl Into<Option<&'a TxHash>>,
|
||||||
|
) -> Self {
|
||||||
|
self.transaction_hash = transaction_hash.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_block_number(&self, number: BlockNumberOrTag) -> BlockNumberOrTag {
|
||||||
|
match self.block_number {
|
||||||
|
Some(block_number) => match number {
|
||||||
|
BlockNumberOrTag::Latest => BlockNumberOrTag::Number(*block_number),
|
||||||
|
n @ (BlockNumberOrTag::Finalized
|
||||||
|
| BlockNumberOrTag::Safe
|
||||||
|
| BlockNumberOrTag::Earliest
|
||||||
|
| BlockNumberOrTag::Pending
|
||||||
|
| BlockNumberOrTag::Number(_)) => n,
|
||||||
|
},
|
||||||
|
None => number,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deployed_contract(
|
||||||
|
&self,
|
||||||
|
instance: &ContractInstance,
|
||||||
|
) -> Option<&(ContractIdent, Address, JsonAbi)> {
|
||||||
|
self.deployed_contracts
|
||||||
|
.and_then(|deployed_contracts| deployed_contracts.get(instance))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deployed_contract_address(&self, instance: &ContractInstance) -> Option<&Address> {
|
||||||
|
self.deployed_contract(instance).map(|(_, a, _)| a)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deployed_contract_abi(&self, instance: &ContractInstance) -> Option<&JsonAbi> {
|
||||||
|
self.deployed_contract(instance).map(|(_, _, a)| a)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn variable(&self, name: impl AsRef<str>) -> Option<&U256> {
|
||||||
|
self.variables
|
||||||
|
.and_then(|variables| variables.get(name.as_ref()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tip_block_number(&self) -> Option<&'a BlockNumber> {
|
||||||
|
self.block_number
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transaction_hash(&self) -> Option<&'a TxHash> {
|
||||||
|
self.transaction_hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,3 +11,6 @@ rust-version.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
alloy = { workspace = true }
|
alloy = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -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 }
|
||||||
@@ -28,3 +29,6 @@ sp-runtime = { workspace = true }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
temp-dir = { workspace = true }
|
temp-dir = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
+201
-118
@@ -3,9 +3,13 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fs::{File, OpenOptions, create_dir_all, remove_dir_all},
|
fs::{File, OpenOptions, create_dir_all, remove_dir_all},
|
||||||
io::{BufRead, BufReader, Read, Write},
|
io::{BufRead, BufReader, Read, Write},
|
||||||
|
ops::ControlFlow,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::{Child, Command, Stdio},
|
process::{Child, Command, Stdio},
|
||||||
sync::atomic::{AtomicU32, Ordering},
|
sync::{
|
||||||
|
Arc,
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -13,23 +17,31 @@ 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, 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},
|
||||||
},
|
},
|
||||||
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 revive_dt_common::fs::clear_directory;
|
use anyhow::Context;
|
||||||
|
use revive_common::EVMVersion;
|
||||||
|
use tracing::{Instrument, instrument};
|
||||||
|
|
||||||
|
use revive_dt_common::{
|
||||||
|
fs::clear_directory,
|
||||||
|
futures::{PollingWaitBehavior, poll},
|
||||||
|
};
|
||||||
use revive_dt_config::Arguments;
|
use revive_dt_config::Arguments;
|
||||||
use revive_dt_format::traits::ResolverApi;
|
use revive_dt_format::traits::ResolverApi;
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
use tracing::Level;
|
|
||||||
|
|
||||||
use crate::{Node, common::FallbackGasFiller, constants::INITIAL_BALANCE};
|
use crate::{Node, common::FallbackGasFiller, constants::INITIAL_BALANCE};
|
||||||
|
|
||||||
@@ -43,6 +55,7 @@ static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
|
|||||||
///
|
///
|
||||||
/// Prunes the child process and the base directory on drop.
|
/// Prunes the child process and the base directory on drop.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
pub struct GethNode {
|
pub struct GethNode {
|
||||||
connection_string: String,
|
connection_string: String,
|
||||||
base_directory: PathBuf,
|
base_directory: PathBuf,
|
||||||
@@ -51,10 +64,10 @@ 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: Arc<EthereumWallet>,
|
||||||
nonce_manager: CachedNonceManager,
|
nonce_manager: CachedNonceManager,
|
||||||
|
chain_id_filler: ChainIdFiller,
|
||||||
/// This vector stores [`File`] objects that we use for logging which we want to flush when the
|
/// This vector stores [`File`] objects that we use for logging which we want to flush when the
|
||||||
/// node object is dropped. We do not store them in a structured fashion at the moment (in
|
/// node object is dropped. We do not store them in a structured fashion at the moment (in
|
||||||
/// separate fields) as the logic that we need to apply to them is all the same regardless of
|
/// separate fields) as the logic that we need to apply to them is all the same regardless of
|
||||||
@@ -77,9 +90,13 @@ impl GethNode {
|
|||||||
const GETH_STDERR_LOG_FILE_NAME: &str = "node_stderr.log";
|
const GETH_STDERR_LOG_FILE_NAME: &str = "node_stderr.log";
|
||||||
|
|
||||||
const TRANSACTION_INDEXING_ERROR: &str = "transaction indexing is in progress";
|
const TRANSACTION_INDEXING_ERROR: &str = "transaction indexing is in progress";
|
||||||
|
const TRANSACTION_TRACING_ERROR: &str = "historical state not available in path scheme yet";
|
||||||
|
|
||||||
|
const RECEIPT_POLLING_DURATION: Duration = Duration::from_secs(5 * 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))]
|
#[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);
|
||||||
@@ -102,6 +119,8 @@ impl GethNode {
|
|||||||
serde_json::to_writer(File::create(&genesis_path)?, &genesis)?;
|
serde_json::to_writer(File::create(&genesis_path)?, &genesis)?;
|
||||||
|
|
||||||
let mut child = Command::new(&self.geth)
|
let mut child = Command::new(&self.geth)
|
||||||
|
.arg("--state.scheme")
|
||||||
|
.arg("hash")
|
||||||
.arg("init")
|
.arg("init")
|
||||||
.arg("--datadir")
|
.arg("--datadir")
|
||||||
.arg(&self.data_directory)
|
.arg(&self.data_directory)
|
||||||
@@ -127,7 +146,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))]
|
#[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:
|
||||||
@@ -150,8 +169,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")
|
||||||
@@ -159,6 +176,12 @@ impl GethNode {
|
|||||||
.arg("0")
|
.arg("0")
|
||||||
.arg("--cache.blocklogs")
|
.arg("--cache.blocklogs")
|
||||||
.arg("512")
|
.arg("512")
|
||||||
|
.arg("--state.scheme")
|
||||||
|
.arg("hash")
|
||||||
|
.arg("--syncmode")
|
||||||
|
.arg("full")
|
||||||
|
.arg("--gcmode")
|
||||||
|
.arg("archive")
|
||||||
.stderr(stderr_logs_file.try_clone()?)
|
.stderr(stderr_logs_file.try_clone()?)
|
||||||
.stdout(stdout_logs_file.try_clone()?)
|
.stdout(stdout_logs_file.try_clone()?)
|
||||||
.spawn()?
|
.spawn()?
|
||||||
@@ -179,7 +202,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))]
|
#[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();
|
||||||
|
|
||||||
@@ -192,6 +215,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) {
|
||||||
@@ -200,142 +224,145 @@ 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")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id), level = Level::TRACE)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn geth_stdout_log_file_path(&self) -> PathBuf {
|
fn geth_stdout_log_file_path(&self) -> PathBuf {
|
||||||
self.logs_directory.join(Self::GETH_STDOUT_LOG_FILE_NAME)
|
self.logs_directory.join(Self::GETH_STDOUT_LOG_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id), level = Level::TRACE)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn geth_stderr_log_file_path(&self) -> PathBuf {
|
fn geth_stderr_log_file_path(&self) -> PathBuf {
|
||||||
self.logs_directory.join(Self::GETH_STDERR_LOG_FILE_NAME)
|
self.logs_directory.join(Self::GETH_STDERR_LOG_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provider(
|
async fn provider(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Future<
|
) -> anyhow::Result<FillProvider<impl TxFiller<Ethereum>, impl Provider<Ethereum>, Ethereum>>
|
||||||
Output = anyhow::Result<
|
{
|
||||||
FillProvider<impl TxFiller<Ethereum>, impl Provider<Ethereum>, Ethereum>,
|
|
||||||
>,
|
|
||||||
> + 'static {
|
|
||||||
let connection_string = self.connection_string();
|
|
||||||
let wallet = self.wallet.clone();
|
|
||||||
|
|
||||||
// Note: We would like all providers to make use of the same nonce manager so that we have
|
|
||||||
// monotonically increasing nonces that are cached. The cached nonce manager uses Arc's in
|
|
||||||
// its implementation and therefore it means that when we clone it then it still references
|
|
||||||
// the same state.
|
|
||||||
let nonce_manager = self.nonce_manager.clone();
|
|
||||||
|
|
||||||
Box::pin(async move {
|
|
||||||
ProviderBuilder::new()
|
ProviderBuilder::new()
|
||||||
.disable_recommended_fillers()
|
.disable_recommended_fillers()
|
||||||
.filler(FallbackGasFiller::new(500_000_000, 500_000_000, 1))
|
.filler(FallbackGasFiller::new(
|
||||||
.filler(ChainIdFiller::default())
|
25_000_000,
|
||||||
.filler(NonceFiller::new(nonce_manager))
|
1_000_000_000,
|
||||||
.wallet(wallet)
|
1_000_000_000,
|
||||||
.connect(&connection_string)
|
))
|
||||||
|
.filler(self.chain_id_filler.clone())
|
||||||
|
.filler(NonceFiller::new(self.nonce_manager.clone()))
|
||||||
|
.wallet(self.wallet.clone())
|
||||||
|
.connect(&self.connection_string)
|
||||||
.await
|
.await
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EthereumNode for GethNode {
|
impl EthereumNode for GethNode {
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[instrument(
|
||||||
|
level = "info",
|
||||||
|
skip_all,
|
||||||
|
fields(geth_node_id = self.id, connection_string = self.connection_string),
|
||||||
|
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 outer_span = tracing::debug_span!("Submitting transaction", ?transaction);
|
|
||||||
let _outer_guard = outer_span.enter();
|
|
||||||
|
|
||||||
let provider = self.provider().await?;
|
let provider = self.provider().await?;
|
||||||
|
|
||||||
let pending_transaction = provider.send_transaction(transaction).await?;
|
let pending_transaction = provider.send_transaction(transaction).await.inspect_err(
|
||||||
let transaction_hash = pending_transaction.tx_hash();
|
|err| tracing::error!(%err, "Encountered an error when submitting the transaction"),
|
||||||
|
)?;
|
||||||
|
let transaction_hash = *pending_transaction.tx_hash();
|
||||||
|
|
||||||
let span = tracing::info_span!("Awaiting transaction receipt", ?transaction_hash);
|
// The following is a fix for the "transaction indexing is in progress" error that we used
|
||||||
let _guard = span.enter();
|
// to get. You can find more information on this in the following GH issue in geth
|
||||||
|
|
||||||
// The following is a fix for the "transaction indexing is in progress" error that we
|
|
||||||
// used to get. You can find more information on this in the following GH issue in geth
|
|
||||||
// https://github.com/ethereum/go-ethereum/issues/28877. To summarize what's going on,
|
// https://github.com/ethereum/go-ethereum/issues/28877. To summarize what's going on,
|
||||||
// before we can get the receipt of the transaction it needs to have been indexed by the
|
// before we can get the receipt of the transaction it needs to have been indexed by the
|
||||||
// node's indexer. Just because the transaction has been confirmed it doesn't mean that
|
// node's indexer. Just because the transaction has been confirmed it doesn't mean that it
|
||||||
// it has been indexed. When we call alloy's `get_receipt` it checks if the transaction
|
// has been indexed. When we call alloy's `get_receipt` it checks if the transaction was
|
||||||
// was confirmed. If it has been, then it will call `eth_getTransactionReceipt` method
|
// confirmed. If it has been, then it will call `eth_getTransactionReceipt` method which
|
||||||
// which _might_ return the above error if the tx has not yet been indexed yet. So, we
|
// _might_ return the above error if the tx has not yet been indexed yet. So, we need to
|
||||||
// need to implement a retry mechanism for the receipt to keep retrying to get it until
|
// implement a retry mechanism for the receipt to keep retrying to get it until it
|
||||||
// it eventually works, but we only do that if the error we get back is the "transaction
|
// eventually works, but we only do that if the error we get back is the "transaction
|
||||||
// indexing is in progress" error or if the receipt is None.
|
// indexing is in progress" error or if the receipt is None.
|
||||||
//
|
//
|
||||||
// Getting the transaction indexed and taking a receipt can take a long time especially
|
// Getting the transaction indexed and taking a receipt can take a long time especially when
|
||||||
// when a lot of transactions are being submitted to the node. Thus, while initially we
|
// a lot of transactions are being submitted to the node. Thus, while initially we only
|
||||||
// only allowed for 60 seconds of waiting with a 1 second delay in polling, we need to
|
// allowed for 60 seconds of waiting with a 1 second delay in polling, we need to allow for
|
||||||
// allow for a larger wait time. Therefore, in here we allow for 5 minutes of waiting
|
// a larger wait time. Therefore, in here we allow for 5 minutes of waiting with exponential
|
||||||
// with exponential backoff each time we attempt to get the receipt and find that it's
|
// backoff each time we attempt to get the receipt and find that it's not available.
|
||||||
// not available.
|
let provider = Arc::new(provider);
|
||||||
let mut retries = 0;
|
poll(
|
||||||
let mut total_wait_duration = Duration::from_secs(0);
|
Self::RECEIPT_POLLING_DURATION,
|
||||||
let max_allowed_wait_duration = Duration::from_secs(5 * 60);
|
PollingWaitBehavior::Constant(Duration::from_millis(200)),
|
||||||
loop {
|
move || {
|
||||||
if total_wait_duration >= max_allowed_wait_duration {
|
let provider = provider.clone();
|
||||||
tracing::error!(
|
async move {
|
||||||
?total_wait_duration,
|
match provider.get_transaction_receipt(transaction_hash).await {
|
||||||
?max_allowed_wait_duration,
|
Ok(Some(receipt)) => Ok(ControlFlow::Break(receipt)),
|
||||||
retry_count = retries,
|
Ok(None) => Ok(ControlFlow::Continue(())),
|
||||||
"Failed to get receipt after polling for it"
|
|
||||||
);
|
|
||||||
anyhow::bail!(
|
|
||||||
"Polled for receipt for {total_wait_duration:?} but failed to get it"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
match provider.get_transaction_receipt(*transaction_hash).await {
|
|
||||||
Ok(Some(receipt)) => {
|
|
||||||
tracing::info!(?total_wait_duration, "Found receipt");
|
|
||||||
break Ok(receipt);
|
|
||||||
}
|
|
||||||
Ok(None) => {}
|
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
let error_string = error.to_string();
|
let error_string = error.to_string();
|
||||||
if !error_string.contains(Self::TRANSACTION_INDEXING_ERROR) {
|
match error_string.contains(Self::TRANSACTION_INDEXING_ERROR) {
|
||||||
break Err(error.into());
|
true => Ok(ControlFlow::Continue(())),
|
||||||
|
false => Err(error.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let next_wait_duration = Duration::from_secs(2u64.pow(retries))
|
|
||||||
.min(max_allowed_wait_duration - total_wait_duration);
|
|
||||||
total_wait_duration += next_wait_duration;
|
|
||||||
retries += 1;
|
|
||||||
|
|
||||||
tokio::time::sleep(next_wait_duration).await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.instrument(tracing::info_span!(
|
||||||
|
"Awaiting transaction receipt",
|
||||||
|
?transaction_hash
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn trace_transaction(
|
async fn trace_transaction(
|
||||||
&self,
|
&self,
|
||||||
transaction: &TransactionReceipt,
|
transaction: &TransactionReceipt,
|
||||||
trace_options: GethDebugTracingOptions,
|
trace_options: GethDebugTracingOptions,
|
||||||
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
|
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
|
||||||
let tx_hash = transaction.transaction_hash;
|
let provider = Arc::new(self.provider().await?);
|
||||||
Ok(self
|
poll(
|
||||||
.provider()
|
Self::TRACE_POLLING_DURATION,
|
||||||
.await?
|
PollingWaitBehavior::Constant(Duration::from_millis(200)),
|
||||||
.debug_trace_transaction(tx_hash, trace_options)
|
move || {
|
||||||
.await?)
|
let provider = provider.clone();
|
||||||
|
let trace_options = trace_options.clone();
|
||||||
|
async move {
|
||||||
|
match provider
|
||||||
|
.debug_trace_transaction(transaction.transaction_hash, trace_options)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(trace) => Ok(ControlFlow::Break(trace)),
|
||||||
|
Err(error) => {
|
||||||
|
let error_string = error.to_string();
|
||||||
|
match error_string.contains(Self::TRANSACTION_TRACING_ERROR) {
|
||||||
|
true => Ok(ControlFlow::Continue(())),
|
||||||
|
false => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result<DiffMode> {
|
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),
|
||||||
@@ -351,10 +378,33 @@ impl EthereumNode for GethNode {
|
|||||||
_ => anyhow::bail!("expected a diff mode trace"),
|
_ => anyhow::bail!("expected a diff mode trace"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
|
async fn balance_of(&self, address: Address) -> anyhow::Result<U256> {
|
||||||
|
self.provider()
|
||||||
|
.await?
|
||||||
|
.get_balance(address)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
|
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))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
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?
|
||||||
@@ -363,7 +413,17 @@ impl ResolverApi for GethNode {
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
|
async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result<u128> {
|
||||||
|
self.provider()
|
||||||
|
.await?
|
||||||
|
.get_transaction_receipt(*tx_hash)
|
||||||
|
.await?
|
||||||
|
.context("Failed to get the transaction receipt")
|
||||||
|
.map(|receipt| receipt.effective_gas_price)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
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?
|
||||||
@@ -373,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))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
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?
|
||||||
@@ -383,17 +443,32 @@ impl ResolverApi for GethNode {
|
|||||||
.map(|block| block.header.beneficiary)
|
.map(|block| block.header.beneficiary)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result<U256> {
|
async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result<U256> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
.get_block_by_number(number)
|
.get_block_by_number(number)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
||||||
.map(|block| block.header.difficulty)
|
.map(|block| U256::from_be_bytes(block.header.mix_hash.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
|
async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result<u64> {
|
||||||
|
self.provider()
|
||||||
|
.await?
|
||||||
|
.get_block_by_number(number)
|
||||||
|
.await?
|
||||||
|
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
||||||
|
.and_then(|block| {
|
||||||
|
block
|
||||||
|
.header
|
||||||
|
.base_fee_per_gas
|
||||||
|
.context("Failed to get the base fee per gas")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
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?
|
||||||
@@ -403,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))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
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?
|
||||||
@@ -413,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))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
async fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -446,22 +521,27 @@ 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: Arc::new(wallet),
|
||||||
|
chain_id_filler: Default::default(),
|
||||||
|
nonce_manager: Default::default(),
|
||||||
// We know that we only need to be storing 2 files so we can specify that when creating
|
// We know that we only need to be storing 2 files so we can specify that when creating
|
||||||
// the vector. It's the stdout and stderr of the geth node.
|
// the vector. It's the stdout and stderr of the geth node.
|
||||||
logs_file_to_flush: Vec::with_capacity(2),
|
logs_file_to_flush: Vec::with_capacity(2),
|
||||||
nonce_manager: Default::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn id(&self) -> usize {
|
||||||
|
self.id as _
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn connection_string(&self) -> String {
|
fn connection_string(&self) -> String {
|
||||||
self.connection_string.clone()
|
self.connection_string.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn shutdown(&mut self) -> anyhow::Result<()> {
|
fn shutdown(&mut self) -> anyhow::Result<()> {
|
||||||
// Terminate the processes in a graceful manner to allow for the output to be flushed.
|
// 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() {
|
||||||
@@ -483,13 +563,13 @@ impl Node for GethNode {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
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))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
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")
|
||||||
@@ -502,17 +582,20 @@ 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))]
|
fn matches_target(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))]
|
#[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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ use std::{
|
|||||||
io::{BufRead, Write},
|
io::{BufRead, Write},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::{Child, Command, Stdio},
|
process::{Child, Command, Stdio},
|
||||||
sync::atomic::{AtomicU32, Ordering},
|
sync::{
|
||||||
|
Arc,
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -16,7 +19,8 @@ use alloy::{
|
|||||||
TransactionBuilderError, UnbuiltTransactionError,
|
TransactionBuilderError, UnbuiltTransactionError,
|
||||||
},
|
},
|
||||||
primitives::{
|
primitives::{
|
||||||
Address, B64, B256, BlockHash, BlockNumber, BlockTimestamp, Bloom, Bytes, FixedBytes, U256,
|
Address, B64, B256, BlockHash, BlockNumber, BlockTimestamp, Bloom, Bytes, FixedBytes,
|
||||||
|
StorageKey, TxHash, U256,
|
||||||
},
|
},
|
||||||
providers::{
|
providers::{
|
||||||
Provider, ProviderBuilder,
|
Provider, ProviderBuilder,
|
||||||
@@ -24,19 +28,20 @@ use alloy::{
|
|||||||
fillers::{CachedNonceManager, ChainIdFiller, FillProvider, NonceFiller, TxFiller},
|
fillers::{CachedNonceManager, ChainIdFiller, FillProvider, NonceFiller, TxFiller},
|
||||||
},
|
},
|
||||||
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 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};
|
||||||
use serde_json::{Value as JsonValue, json};
|
use serde_json::{Value as JsonValue, json};
|
||||||
use sp_core::crypto::Ss58Codec;
|
use sp_core::crypto::Ss58Codec;
|
||||||
use sp_runtime::AccountId32;
|
use sp_runtime::AccountId32;
|
||||||
use tracing::Level;
|
|
||||||
|
|
||||||
use revive_dt_config::Arguments;
|
use revive_dt_config::Arguments;
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
@@ -51,12 +56,13 @@ pub struct KitchensinkNode {
|
|||||||
substrate_binary: PathBuf,
|
substrate_binary: PathBuf,
|
||||||
eth_proxy_binary: PathBuf,
|
eth_proxy_binary: PathBuf,
|
||||||
rpc_url: String,
|
rpc_url: String,
|
||||||
wallet: EthereumWallet,
|
|
||||||
base_directory: PathBuf,
|
base_directory: PathBuf,
|
||||||
logs_directory: PathBuf,
|
logs_directory: PathBuf,
|
||||||
process_substrate: Option<Child>,
|
process_substrate: Option<Child>,
|
||||||
process_proxy: Option<Child>,
|
process_proxy: Option<Child>,
|
||||||
|
wallet: Arc<EthereumWallet>,
|
||||||
nonce_manager: CachedNonceManager,
|
nonce_manager: CachedNonceManager,
|
||||||
|
chain_id_filler: ChainIdFiller,
|
||||||
/// This vector stores [`File`] objects that we use for logging which we want to flush when the
|
/// This vector stores [`File`] objects that we use for logging which we want to flush when the
|
||||||
/// node object is dropped. We do not store them in a structured fashion at the moment (in
|
/// node object is dropped. We do not store them in a structured fashion at the moment (in
|
||||||
/// separate fields) as the logic that we need to apply to them is all the same regardless of
|
/// separate fields) as the logic that we need to apply to them is all the same regardless of
|
||||||
@@ -84,7 +90,6 @@ impl KitchensinkNode {
|
|||||||
const PROXY_STDOUT_LOG_FILE_NAME: &str = "proxy_stdout.log";
|
const PROXY_STDOUT_LOG_FILE_NAME: &str = "proxy_stdout.log";
|
||||||
const PROXY_STDERR_LOG_FILE_NAME: &str = "proxy_stderr.log";
|
const PROXY_STDERR_LOG_FILE_NAME: &str = "proxy_stderr.log";
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
fn init(&mut self, genesis: &str) -> anyhow::Result<&mut Self> {
|
fn init(&mut self, genesis: &str) -> 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);
|
||||||
@@ -157,7 +162,6 @@ impl KitchensinkNode {
|
|||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
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;
|
||||||
@@ -209,12 +213,8 @@ impl KitchensinkNode {
|
|||||||
if let Err(error) = Self::wait_ready(
|
if let Err(error) = Self::wait_ready(
|
||||||
self.kitchensink_stderr_log_file_path().as_path(),
|
self.kitchensink_stderr_log_file_path().as_path(),
|
||||||
Self::SUBSTRATE_READY_MARKER,
|
Self::SUBSTRATE_READY_MARKER,
|
||||||
Duration::from_secs(30),
|
Duration::from_secs(60),
|
||||||
) {
|
) {
|
||||||
tracing::error!(
|
|
||||||
?error,
|
|
||||||
"Failed to start substrate, shutting down gracefully"
|
|
||||||
);
|
|
||||||
self.shutdown()?;
|
self.shutdown()?;
|
||||||
return Err(error);
|
return Err(error);
|
||||||
};
|
};
|
||||||
@@ -238,9 +238,8 @@ impl KitchensinkNode {
|
|||||||
if let Err(error) = Self::wait_ready(
|
if let Err(error) = Self::wait_ready(
|
||||||
self.proxy_stderr_log_file_path().as_path(),
|
self.proxy_stderr_log_file_path().as_path(),
|
||||||
Self::ETH_PROXY_READY_MARKER,
|
Self::ETH_PROXY_READY_MARKER,
|
||||||
Duration::from_secs(30),
|
Duration::from_secs(60),
|
||||||
) {
|
) {
|
||||||
tracing::error!(?error, "Failed to start proxy, shutting down gracefully");
|
|
||||||
self.shutdown()?;
|
self.shutdown()?;
|
||||||
return Err(error);
|
return Err(error);
|
||||||
};
|
};
|
||||||
@@ -255,7 +254,6 @@ impl KitchensinkNode {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
fn extract_balance_from_genesis_file(
|
fn extract_balance_from_genesis_file(
|
||||||
&self,
|
&self,
|
||||||
genesis: &Genesis,
|
genesis: &Genesis,
|
||||||
@@ -304,7 +302,6 @@ impl KitchensinkNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
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")
|
||||||
@@ -317,74 +314,55 @@ impl KitchensinkNode {
|
|||||||
Ok(String::from_utf8_lossy(&output).trim().to_string())
|
Ok(String::from_utf8_lossy(&output).trim().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), level = Level::TRACE)]
|
|
||||||
fn kitchensink_stdout_log_file_path(&self) -> PathBuf {
|
fn kitchensink_stdout_log_file_path(&self) -> PathBuf {
|
||||||
self.logs_directory
|
self.logs_directory
|
||||||
.join(Self::KITCHENSINK_STDOUT_LOG_FILE_NAME)
|
.join(Self::KITCHENSINK_STDOUT_LOG_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), level = Level::TRACE)]
|
|
||||||
fn kitchensink_stderr_log_file_path(&self) -> PathBuf {
|
fn kitchensink_stderr_log_file_path(&self) -> PathBuf {
|
||||||
self.logs_directory
|
self.logs_directory
|
||||||
.join(Self::KITCHENSINK_STDERR_LOG_FILE_NAME)
|
.join(Self::KITCHENSINK_STDERR_LOG_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), level = Level::TRACE)]
|
|
||||||
fn proxy_stdout_log_file_path(&self) -> PathBuf {
|
fn proxy_stdout_log_file_path(&self) -> PathBuf {
|
||||||
self.logs_directory.join(Self::PROXY_STDOUT_LOG_FILE_NAME)
|
self.logs_directory.join(Self::PROXY_STDOUT_LOG_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), level = Level::TRACE)]
|
|
||||||
fn proxy_stderr_log_file_path(&self) -> PathBuf {
|
fn proxy_stderr_log_file_path(&self) -> PathBuf {
|
||||||
self.logs_directory.join(Self::PROXY_STDERR_LOG_FILE_NAME)
|
self.logs_directory.join(Self::PROXY_STDERR_LOG_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provider(
|
async fn provider(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Future<
|
) -> anyhow::Result<
|
||||||
Output = anyhow::Result<
|
|
||||||
FillProvider<
|
FillProvider<
|
||||||
impl TxFiller<KitchenSinkNetwork>,
|
impl TxFiller<KitchenSinkNetwork>,
|
||||||
impl Provider<KitchenSinkNetwork>,
|
impl Provider<KitchenSinkNetwork>,
|
||||||
KitchenSinkNetwork,
|
KitchenSinkNetwork,
|
||||||
>,
|
>,
|
||||||
>,
|
> {
|
||||||
> + 'static {
|
|
||||||
let connection_string = self.connection_string();
|
|
||||||
let wallet = self.wallet.clone();
|
|
||||||
|
|
||||||
// Note: We would like all providers to make use of the same nonce manager so that we have
|
|
||||||
// monotonically increasing nonces that are cached. The cached nonce manager uses Arc's in
|
|
||||||
// its implementation and therefore it means that when we clone it then it still references
|
|
||||||
// the same state.
|
|
||||||
let nonce_manager = self.nonce_manager.clone();
|
|
||||||
|
|
||||||
Box::pin(async move {
|
|
||||||
ProviderBuilder::new()
|
ProviderBuilder::new()
|
||||||
.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(self.chain_id_filler.clone())
|
||||||
.filler(NonceFiller::new(nonce_manager))
|
.filler(NonceFiller::new(self.nonce_manager.clone()))
|
||||||
.wallet(wallet)
|
.wallet(self.wallet.clone())
|
||||||
.connect(&connection_string)
|
.connect(&self.rpc_url)
|
||||||
.await
|
.await
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EthereumNode for KitchensinkNode {
|
impl EthereumNode for KitchensinkNode {
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
async fn execute_transaction(
|
async fn execute_transaction(
|
||||||
&self,
|
&self,
|
||||||
transaction: alloy::rpc::types::TransactionRequest,
|
transaction: alloy::rpc::types::TransactionRequest,
|
||||||
) -> anyhow::Result<TransactionReceipt> {
|
) -> anyhow::Result<TransactionReceipt> {
|
||||||
tracing::debug!(?transaction, "Submitting transaction");
|
|
||||||
let receipt = self
|
let receipt = self
|
||||||
.provider()
|
.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -392,11 +370,9 @@ impl EthereumNode for KitchensinkNode {
|
|||||||
.await?
|
.await?
|
||||||
.get_receipt()
|
.get_receipt()
|
||||||
.await?;
|
.await?;
|
||||||
tracing::info!(?receipt, "Submitted tx to kitchensink");
|
|
||||||
Ok(receipt)
|
Ok(receipt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
async fn trace_transaction(
|
async fn trace_transaction(
|
||||||
&self,
|
&self,
|
||||||
transaction: &TransactionReceipt,
|
transaction: &TransactionReceipt,
|
||||||
@@ -410,7 +386,6 @@ impl EthereumNode for KitchensinkNode {
|
|||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
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),
|
||||||
@@ -426,10 +401,30 @@ impl EthereumNode for KitchensinkNode {
|
|||||||
_ => anyhow::bail!("expected a diff mode trace"),
|
_ => anyhow::bail!("expected a diff mode trace"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn balance_of(&self, address: Address) -> anyhow::Result<U256> {
|
||||||
|
self.provider()
|
||||||
|
.await?
|
||||||
|
.get_balance(address)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
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))]
|
|
||||||
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?
|
||||||
@@ -438,7 +433,15 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result<u128> {
|
||||||
|
self.provider()
|
||||||
|
.await?
|
||||||
|
.get_transaction_receipt(*tx_hash)
|
||||||
|
.await?
|
||||||
|
.context("Failed to get the transaction receipt")
|
||||||
|
.map(|receipt| receipt.effective_gas_price)
|
||||||
|
}
|
||||||
|
|
||||||
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?
|
||||||
@@ -448,7 +451,6 @@ 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))]
|
|
||||||
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?
|
||||||
@@ -458,17 +460,29 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map(|block| block.header.beneficiary)
|
.map(|block| block.header.beneficiary)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
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?
|
||||||
.get_block_by_number(number)
|
.get_block_by_number(number)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
||||||
.map(|block| block.header.difficulty)
|
.map(|block| U256::from_be_bytes(block.header.mix_hash.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result<u64> {
|
||||||
|
self.provider()
|
||||||
|
.await?
|
||||||
|
.get_block_by_number(number)
|
||||||
|
.await?
|
||||||
|
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
||||||
|
.and_then(|block| {
|
||||||
|
block
|
||||||
|
.header
|
||||||
|
.base_fee_per_gas
|
||||||
|
.context("Failed to get the base fee per gas")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
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?
|
||||||
@@ -478,7 +492,6 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map(|block| block.header.hash)
|
.map(|block| block.header.hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
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?
|
||||||
@@ -488,7 +501,6 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map(|block| block.header.timestamp)
|
.map(|block| block.header.timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
async fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
async fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -519,11 +531,12 @@ impl Node for KitchensinkNode {
|
|||||||
substrate_binary: config.kitchensink.clone(),
|
substrate_binary: config.kitchensink.clone(),
|
||||||
eth_proxy_binary: config.eth_proxy.clone(),
|
eth_proxy_binary: config.eth_proxy.clone(),
|
||||||
rpc_url: String::new(),
|
rpc_url: String::new(),
|
||||||
wallet,
|
|
||||||
base_directory,
|
base_directory,
|
||||||
logs_directory,
|
logs_directory,
|
||||||
process_substrate: None,
|
process_substrate: None,
|
||||||
process_proxy: None,
|
process_proxy: None,
|
||||||
|
wallet: Arc::new(wallet),
|
||||||
|
chain_id_filler: Default::default(),
|
||||||
nonce_manager: Default::default(),
|
nonce_manager: Default::default(),
|
||||||
// We know that we only need to be storing 4 files so we can specify that when creating
|
// We know that we only need to be storing 4 files so we can specify that when creating
|
||||||
// the vector. It's the stdout and stderr of the substrate-node and the eth-rpc.
|
// the vector. It's the stdout and stderr of the substrate-node and the eth-rpc.
|
||||||
@@ -531,12 +544,14 @@ impl Node for KitchensinkNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
fn id(&self) -> usize {
|
||||||
|
self.id as _
|
||||||
|
}
|
||||||
|
|
||||||
fn connection_string(&self) -> String {
|
fn connection_string(&self) -> String {
|
||||||
self.rpc_url.clone()
|
self.rpc_url.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
fn shutdown(&mut self) -> anyhow::Result<()> {
|
fn shutdown(&mut self) -> anyhow::Result<()> {
|
||||||
// Terminate the processes in a graceful manner to allow for the output to be flushed.
|
// 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() {
|
||||||
@@ -563,12 +578,10 @@ impl Node for KitchensinkNode {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
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))]
|
|
||||||
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")
|
||||||
@@ -581,17 +594,19 @@ impl Node for KitchensinkNode {
|
|||||||
Ok(String::from_utf8_lossy(&output).into())
|
Ok(String::from_utf8_lossy(&output).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
fn matches_target(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() == "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 {
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.shutdown().expect("Failed to shutdown")
|
self.shutdown().expect("Failed to shutdown")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -17,6 +18,9 @@ pub trait Node: EthereumNode {
|
|||||||
/// Create a new uninitialized instance.
|
/// Create a new uninitialized instance.
|
||||||
fn new(config: &Arguments) -> Self;
|
fn new(config: &Arguments) -> Self;
|
||||||
|
|
||||||
|
/// Returns the identifier of the node.
|
||||||
|
fn id(&self) -> usize;
|
||||||
|
|
||||||
/// Spawns a node configured according to the genesis json.
|
/// Spawns a node configured according to the genesis json.
|
||||||
///
|
///
|
||||||
/// Blocking until it's ready to accept transactions.
|
/// Blocking until it's ready to accept transactions.
|
||||||
@@ -35,5 +39,8 @@ 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(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;
|
||||||
|
|
||||||
@@ -62,7 +63,6 @@ where
|
|||||||
|
|
||||||
fn spawn_node<T: Node + Send>(args: &Arguments, genesis: String) -> anyhow::Result<T> {
|
fn spawn_node<T: Node + Send>(args: &Arguments, genesis: String) -> anyhow::Result<T> {
|
||||||
let mut node = T::new(args);
|
let mut node = T::new(args);
|
||||||
tracing::info!("starting node: {}", node.connection_string());
|
|
||||||
node.spawn(genesis)?;
|
node.spawn(genesis)?;
|
||||||
Ok(node)
|
Ok(node)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,14 @@ 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 }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
tracing = { workspace = true }
|
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
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,
|
||||||
@@ -184,8 +185,6 @@ impl Report {
|
|||||||
let file = File::create(&path).context(path.display().to_string())?;
|
let file = File::create(&path).context(path.display().to_string())?;
|
||||||
serde_json::to_writer_pretty(file, &self)?;
|
serde_json::to_writer_pretty(file, &self)?;
|
||||||
|
|
||||||
tracing::info!("report written to: {}", path.display());
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,3 +19,6 @@ reqwest = { workspace = true }
|
|||||||
semver = { workspace = true }
|
semver = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
sha2 = { workspace = true }
|
sha2 = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ use std::{
|
|||||||
|
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::download::GHDownloader;
|
use crate::download::SolcDownloader;
|
||||||
|
|
||||||
pub const SOLC_CACHE_DIRECTORY: &str = "solc";
|
pub const SOLC_CACHE_DIRECTORY: &str = "solc";
|
||||||
pub(crate) static SOLC_CACHER: LazyLock<Mutex<HashSet<PathBuf>>> = LazyLock::new(Default::default);
|
pub(crate) static SOLC_CACHER: LazyLock<Mutex<HashSet<PathBuf>>> = LazyLock::new(Default::default);
|
||||||
|
|
||||||
pub(crate) async fn get_or_download(
|
pub(crate) async fn get_or_download(
|
||||||
working_directory: &Path,
|
working_directory: &Path,
|
||||||
downloader: &GHDownloader,
|
downloader: &SolcDownloader,
|
||||||
) -> anyhow::Result<PathBuf> {
|
) -> anyhow::Result<PathBuf> {
|
||||||
let target_directory = working_directory
|
let target_directory = working_directory
|
||||||
.join(SOLC_CACHE_DIRECTORY)
|
.join(SOLC_CACHE_DIRECTORY)
|
||||||
@@ -38,11 +38,8 @@ pub(crate) async fn get_or_download(
|
|||||||
Ok(target_file)
|
Ok(target_file)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_to_file(path: &Path, downloader: &GHDownloader) -> anyhow::Result<()> {
|
async fn download_to_file(path: &Path, downloader: &SolcDownloader) -> anyhow::Result<()> {
|
||||||
tracing::info!("caching file: {}", path.display());
|
|
||||||
|
|
||||||
let Ok(file) = File::create_new(path) else {
|
let Ok(file) = File::create_new(path) else {
|
||||||
tracing::debug!("cache file already exists: {}", path.display());
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -38,21 +38,21 @@ impl List {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Download solc binaries from GitHub releases (IPFS links aren't reliable).
|
/// Download solc binaries from the official SolidityLang site
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct GHDownloader {
|
pub struct SolcDownloader {
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
pub target: &'static str,
|
pub target: &'static str,
|
||||||
pub list: &'static str,
|
pub list: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GHDownloader {
|
impl SolcDownloader {
|
||||||
pub const BASE_URL: &str = "https://github.com/ethereum/solidity/releases/download";
|
pub const BASE_URL: &str = "https://binaries.soliditylang.org";
|
||||||
|
|
||||||
pub const LINUX_NAME: &str = "solc-static-linux";
|
pub const LINUX_NAME: &str = "linux-amd64";
|
||||||
pub const MACOSX_NAME: &str = "solc-macos";
|
pub const MACOSX_NAME: &str = "macosx-amd64";
|
||||||
pub const WINDOWS_NAME: &str = "solc-windows.exe";
|
pub const WINDOWS_NAME: &str = "windows-amd64";
|
||||||
pub const WASM_NAME: &str = "soljson.js";
|
pub const WASM_NAME: &str = "wasm";
|
||||||
|
|
||||||
async fn new(
|
async fn new(
|
||||||
version: impl Into<VersionOrRequirement>,
|
version: impl Into<VersionOrRequirement>,
|
||||||
@@ -102,26 +102,26 @@ impl GHDownloader {
|
|||||||
Self::new(version, Self::WASM_NAME, List::WASM_URL).await
|
Self::new(version, Self::WASM_NAME, List::WASM_URL).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the download link.
|
|
||||||
pub fn url(&self) -> String {
|
|
||||||
format!("{}/v{}/{}", Self::BASE_URL, &self.version, &self.target)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Download the solc binary.
|
/// Download the solc binary.
|
||||||
///
|
///
|
||||||
/// Errors out if the download fails or the digest of the downloaded file
|
/// Errors out if the download fails or the digest of the downloaded file
|
||||||
/// mismatches the expected digest from the release [List].
|
/// mismatches the expected digest from the release [List].
|
||||||
pub async fn download(&self) -> anyhow::Result<Vec<u8>> {
|
pub async fn download(&self) -> anyhow::Result<Vec<u8>> {
|
||||||
tracing::info!("downloading solc: {self:?}");
|
let builds = List::download(self.list).await?.builds;
|
||||||
let expected_digest = List::download(self.list)
|
let build = builds
|
||||||
.await?
|
|
||||||
.builds
|
|
||||||
.iter()
|
.iter()
|
||||||
.find(|build| build.version == self.version)
|
.find(|build| build.version == self.version)
|
||||||
.ok_or_else(|| anyhow::anyhow!("solc v{} not found builds", self.version))
|
.ok_or_else(|| anyhow::anyhow!("solc v{} not found builds", self.version))?;
|
||||||
.map(|b| b.sha256.strip_prefix("0x").unwrap_or(&b.sha256).to_string())?;
|
|
||||||
|
|
||||||
let file = reqwest::get(self.url()).await?.bytes().await?.to_vec();
|
let path = build.path.clone();
|
||||||
|
let expected_digest = build
|
||||||
|
.sha256
|
||||||
|
.strip_prefix("0x")
|
||||||
|
.unwrap_or(&build.sha256)
|
||||||
|
.to_string();
|
||||||
|
let url = format!("{}/{}/{}", Self::BASE_URL, self.target, path.display());
|
||||||
|
|
||||||
|
let file = reqwest::get(url).await?.bytes().await?.to_vec();
|
||||||
|
|
||||||
if hex::encode(Sha256::digest(&file)) != expected_digest {
|
if hex::encode(Sha256::digest(&file)) != expected_digest {
|
||||||
anyhow::bail!("sha256 mismatch for solc version {}", self.version);
|
anyhow::bail!("sha256 mismatch for solc version {}", self.version);
|
||||||
@@ -133,7 +133,7 @@ impl GHDownloader {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{download::GHDownloader, list::List};
|
use crate::{download::SolcDownloader, list::List};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn try_get_windows() {
|
async fn try_get_windows() {
|
||||||
@@ -141,7 +141,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.latest_release;
|
.latest_release;
|
||||||
GHDownloader::windows(version)
|
SolcDownloader::windows(version)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.download()
|
.download()
|
||||||
@@ -155,7 +155,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.latest_release;
|
.latest_release;
|
||||||
GHDownloader::macosx(version)
|
SolcDownloader::macosx(version)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.download()
|
.download()
|
||||||
@@ -169,7 +169,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.latest_release;
|
.latest_release;
|
||||||
GHDownloader::linux(version)
|
SolcDownloader::linux(version)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.download()
|
.download()
|
||||||
@@ -180,7 +180,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn try_get_wasm() {
|
async fn try_get_wasm() {
|
||||||
let version = List::download(List::WASM_URL).await.unwrap().latest_release;
|
let version = List::download(List::WASM_URL).await.unwrap().latest_release;
|
||||||
GHDownloader::wasm(version)
|
SolcDownloader::wasm(version)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.download()
|
.download()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use cache::get_or_download;
|
use cache::get_or_download;
|
||||||
use download::GHDownloader;
|
use download::SolcDownloader;
|
||||||
|
|
||||||
use revive_dt_common::types::VersionOrRequirement;
|
use revive_dt_common::types::VersionOrRequirement;
|
||||||
|
|
||||||
@@ -25,13 +25,13 @@ pub async fn download_solc(
|
|||||||
wasm: bool,
|
wasm: bool,
|
||||||
) -> anyhow::Result<PathBuf> {
|
) -> anyhow::Result<PathBuf> {
|
||||||
let downloader = if wasm {
|
let downloader = if wasm {
|
||||||
GHDownloader::wasm(version).await
|
SolcDownloader::wasm(version).await
|
||||||
} else if cfg!(target_os = "linux") {
|
} else if cfg!(target_os = "linux") {
|
||||||
GHDownloader::linux(version).await
|
SolcDownloader::linux(version).await
|
||||||
} else if cfg!(target_os = "macos") {
|
} else if cfg!(target_os = "macos") {
|
||||||
GHDownloader::macosx(version).await
|
SolcDownloader::macosx(version).await
|
||||||
} else if cfg!(target_os = "windows") {
|
} else if cfg!(target_os = "windows") {
|
||||||
GHDownloader::windows(version).await
|
SolcDownloader::windows(version).await
|
||||||
} else {
|
} else {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}?;
|
}?;
|
||||||
|
|||||||
Reference in New Issue
Block a user